class PRAcroForm extends PdfDictionary {

    ArrayList<FieldInformation> fields;
    ArrayList<PdfDictionary> stack;
    HashMap<String, FieldInformation> fieldByName;
    PdfReader reader;

    /**
     * Constructor
     *
     * @param reader reader of the input file
     */
    public PRAcroForm(PdfReader reader) {
        this.reader = reader;
        fields = new ArrayList<>();
        fieldByName = new HashMap<>();
        stack = new ArrayList<>();
    }

    /**
     * Number of fields found
     *
     * @return size
     */
    public int size() {
        return fields.size();
    }

    public ArrayList<FieldInformation> getFields() {
        return fields;
    }

    public FieldInformation getField(String name) {
        return fieldByName.get(name);
    }

    /**
     * Given the title (/T) of a reference, return the associated reference
     *
     * @param name a string containing the path
     * @return a reference to the field, or null
     */
    public PRIndirectReference getRefByName(String name) {
        FieldInformation fi = fieldByName.get(name);
        if (fi == null) {
            return null;
        }
        return fi.getRef();
    }

    /**
     * Read, and comprehend the acroform
     *
     * @param root the document root
     */
    public void readAcroForm(PdfDictionary root) {
        if (root == null) {
            return;
        }
        hashMap = root.hashMap;
        pushAttrib(root);
        PdfArray fieldlist = (PdfArray) PdfReader.getPdfObjectRelease(root.get(PdfName.FIELDS));
        iterateFields(fieldlist, null, null);
    }

    /**
     * After reading, we index all of the fields. Recursive.
     *
     * @param fieldlist An array of fields
     * @param fieldDict the last field dictionary we encountered (recursively)
     * @param title     the pathname of the field, up to this point or null
     */
    protected void iterateFields(PdfArray fieldlist, PRIndirectReference fieldDict, String title) {<FILL_FUNCTION_BODY>}

    /**
     * merge field attributes from two dictionaries
     *
     * @param parent one dictionary
     * @param child  the other dictionary
     * @return a merged dictionary
     */
    protected PdfDictionary mergeAttrib(PdfDictionary parent, PdfDictionary child) {
        PdfDictionary targ = new PdfDictionary();
        if (parent != null) {
            targ.putAll(parent);
        }

        for (PdfName key : child.getKeys()) {
            if (key.equals(PdfName.DR) || key.equals(PdfName.DA) ||
                    key.equals(PdfName.Q) || key.equals(PdfName.FF) ||
                    key.equals(PdfName.DV) || key.equals(PdfName.V)
                    || key.equals(PdfName.FT)
                    || key.equals(PdfName.F)) {
                targ.put(key, child.get(key));
            }
        }
        return targ;
    }

    /**
     * stack a level of dictionary. Merge in a dictionary from this level
     *
     * @param dict the PfdDictionary
     */
    protected void pushAttrib(PdfDictionary dict) {
        PdfDictionary dic = null;
        if (!stack.isEmpty()) {
            dic = stack.get(stack.size() - 1);
        }
        dic = mergeAttrib(dic, dict);
        stack.add(dic);
    }

    /**
     * This class holds the information for a single field
     */
    public static class FieldInformation {

        String name;
        PdfDictionary info;
        PRIndirectReference ref;

        FieldInformation(String name, PdfDictionary info, PRIndirectReference ref) {
            this.name = name;
            this.info = info;
            this.ref = ref;
        }

        public String getName() {
            return name;
        }

        public PdfDictionary getInfo() {
            return info;
        }

        public PRIndirectReference getRef() {
            return ref;
        }
    }
}

        for (PdfObject pdfObject : fieldlist.getElements()) {
            PRIndirectReference ref = (PRIndirectReference) pdfObject;
            PdfDictionary dict = (PdfDictionary) PdfReader.getPdfObjectRelease(ref);

            // if we are not a field dictionary, pass our parent's values
            PRIndirectReference myFieldDict = fieldDict;
            String myTitle = title;
            PdfString tField = (PdfString) dict.get(PdfName.T);
            boolean isFieldDict = tField != null;

            if (isFieldDict) {
                myFieldDict = ref;
                if (title == null) {
                    myTitle = tField.toString();
                } else {
                    myTitle = title + '.' + tField.toString();
                }
            }

            PdfArray kids = (PdfArray) dict.get(PdfName.KIDS);
            if (kids != null) {
                pushAttrib(dict);
                List<PdfObject> elements = kids.getElements();
                Iterator<PdfObject> kidIter = elements.iterator();
                while (kidIter.hasNext()) {
                    PdfIndirectReference kidRef = (PdfIndirectReference) kidIter.next();
                    if (ref.getNumber() == kidRef.getNumber()) {
                        kidIter.remove();
                    }
                }
                iterateFields(new PdfArray(elements), myFieldDict, myTitle);
                stack.remove(stack.size() - 1);   // pop
            } else {          // leaf node
                if (myFieldDict != null) {
                    PdfDictionary mergedDict = stack.get(stack.size() - 1);
                    if (isFieldDict) {
                        mergedDict = mergeAttrib(mergedDict, dict);
                    }

                    mergedDict.put(PdfName.T, new PdfString(myTitle));
                    FieldInformation fi = new FieldInformation(myTitle, mergedDict, myFieldDict);
                    fields.add(fi);
                    fieldByName.put(myTitle, fi);
                }
            }
        }
    
<methods>public void <init>() ,public void <init>(com.lowagie.text.pdf.PdfName) ,public void clear() ,public boolean contains(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfObject get(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfArray getAsArray(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfBoolean getAsBoolean(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfDictionary getAsDict(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfIndirectReference getAsIndirectObject(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfName getAsName(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfNumber getAsNumber(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfStream getAsStream(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfString getAsString(com.lowagie.text.pdf.PdfName) ,public com.lowagie.text.pdf.PdfObject getDirectObject(com.lowagie.text.pdf.PdfName) ,public Set<com.lowagie.text.pdf.PdfName> getKeys() ,public Set<Entry<com.lowagie.text.pdf.PdfName,com.lowagie.text.pdf.PdfObject>> getKeysAndValues() ,public boolean isCatalog() ,public boolean isFont() ,public boolean isOutlineTree() ,public boolean isPage() ,public boolean isPages() ,public void merge(com.lowagie.text.pdf.PdfDictionary) ,public void mergeDifferent(com.lowagie.text.pdf.PdfDictionary) ,public void put(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) ,public void putAll(com.lowagie.text.pdf.PdfDictionary) ,public void putEx(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) ,public void remove(com.lowagie.text.pdf.PdfName) ,public int size() ,public void toPdf(com.lowagie.text.pdf.PdfWriter, java.io.OutputStream) throws java.io.IOException,public java.lang.String toString() <variables>public static final com.lowagie.text.pdf.PdfName CATALOG,public static final com.lowagie.text.pdf.PdfName FONT,public static final com.lowagie.text.pdf.PdfName OUTLINES,public static final com.lowagie.text.pdf.PdfName PAGE,public static final com.lowagie.text.pdf.PdfName PAGES,private com.lowagie.text.pdf.PdfName dictionaryType,protected Map<com.lowagie.text.pdf.PdfName,com.lowagie.text.pdf.PdfObject> hashMap