alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.xml.dtd.grammar;

import java.util.*;

/**
 * Implementation of queriable DTD content models. It is a hungry
 * automaton.
 *
 * @see ContentModelTest
 *
 * @author  Petr Kuzel
 */
abstract class ContentModel {
    
    /**
     * Create model by parsing its string representation "|,*?+()WS".
     * Caller must filter out ANY, EMPTY and 
     * (#PCDATA) content models.
     */
    public static final ContentModel parseContentModel(String model) {
        
        if (model == null || model.length() == 0) throw new IllegalArgumentException();

        PushbackStringTokenizer tokens = 
            new PushbackStringTokenizer(model, "|,*?+() \t\n", true);           // NOI18N
        String next = tokens.nextToken();            
        if (next.charAt(0) != '(' ) throw new IllegalStateException();
        return parseContentModel(tokens);
    }

    private static ContentModel parseContentModel(PushbackStringTokenizer tokens) {

        ContentModel model = null;;
        List models = new ArrayList(7);
        char type = 'E';
        char ch;            
        String next;

        do {

            next = tokens.nextToken();
            ch = next.charAt(0);
            if (ch == ' ' || ch == '\t' || ch == '\n') continue;
            if (ch == '#') { // #PCDATA
                do {
                    ch = tokens.nextToken().charAt(0);
                } while (ch == ' ' || ch == '\t' || ch == '\n');
                if (ch != '|') throw new IllegalStateException();
                continue; 
            } else if (ch == '(') {
                models.add(parseContentModel(tokens));
            } else if (ch == '|') {
                type = '|';
            } else if (ch == ',') {
                type = ',';
            } else if (ch == ')') {
                break;
            } else {
                model = new Element(next);

                // optional element multiplicity

                do {
                    next = tokens.nextToken();
                    ch = next.charAt(0);
                } while (ch == ' ' || ch == '\t' || ch == '\n');
                if (ch == '+') {
                    model = new MultiplicityGroup(model, 1, -1);
                } else if (ch == '?') {
                    model = new MultiplicityGroup(model, 0, 1);
                } else if (ch == '*') {
                    model = new MultiplicityGroup(model, 0, -1);
                } else if (ch == ')') {
                    // do not pushback!
                } else {
                    tokens.pushback(next);
                }
                models.add(model);
            }

        } while (ch != ')');

        // create models

        if (type == '|') {
            model = new Choice((ContentModel[])models.toArray(new ContentModel[0]));
        } else if (type == ',') {
            model = new Sequence((ContentModel[])models.toArray(new ContentModel[0]));
        } else {
            // note model contains last Element
        }

        // determine optional group multiplicity

        do {
            if (tokens.hasMoreTokens() == false) break;
            next = tokens.nextToken();
            ch = next.charAt(0);
        } while (ch == ' ' || ch == '\t' || ch == '\n');

        if (ch == '?') {
            model = new MultiplicityGroup(model, 0, 1);
        } else if (ch == '*') {
            model = new MultiplicityGroup(model, 0, -1);
        } else if (ch == '+') {
            model = new MultiplicityGroup(model, 1, -1);
        } else {
            tokens.pushback(next);
        }

//        System.out.println("Model " + model);
        return model;
    }

    /**
     * @return enumeration<String> or null if document is not valid.
     */
    public final Enumeration whatCanFollow(Enumeration en) {
        reset();
        Food food = new Food(en);
        if (eat(food)) {
            return possibilities();
        } else {
            return null;
        }                        
    }

    /**
     * Reinitializes the content model to initial state.
     */
    protected void reset() {
    }

    /**
     * Move the automaton to next state. It is may not be called
     * twice without reset!
     * @return true if accepted the food, false at root model indicate document error
     */
    protected abstract boolean eat(Food food);

    /**
     * Enumerate all FIRSTs at current state.
     * It must not be called if eat returned false
     * @return possible completion
     */
    protected abstract Enumeration possibilities();

    /**
     * Does need the content model a reset because it is in final state?
     * @return true if it is in final state
     */
    protected boolean terminated() {
        return false;
    }
    
    /**
     * Is the content model in current state optional?
     */
    protected boolean isOptional() {
        return false;
    }

    // ~~~~~~~~~~~~~~~~~~~~~~ Implemenation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * Single element
     */
    private static class Element extends ContentModel {

        private final String name;

        private boolean full = false;

        public Element (String name) {
            this.name = name;
        }

        protected void reset() {
            full = false;
        }
        
        protected boolean eat(Food food) {
            if (food.hasNext() == false) {
                return true;
            } else {
                String next = food.next();
                if (this.name.equals(next)) {                
                    full = true;
                    return true;
                } else {
                    return false;
                }
            }
        }

        protected Enumeration possibilities() {
            if (terminated() == false) {
                return org.openide.util.Enumerations.singleton (name);
            } else {
                return org.openide.util.Enumerations.empty();
            }
        }

        protected boolean terminated() {
            return full;
        }
                
        public String toString() {
            return "Element[" + name + "]";
        }
    }

    
    /**
     * Mandatory sequence of models.
     */
    private static class Sequence extends ContentModel {

        private ContentModel[] models;

        // index of current model to use <0, models.length>
        private int current = 0;

        public Sequence(ContentModel[] models) {
            this.models = models;
        }

        /**
         * Reset all models upto (inclusive) current one.
         */
        protected void reset() {            
            for (int i = 0; i move all subsequent automatons
                    // to have accurate possibilities()

                    int level = food.mark();
                    for (int i = current + 1; i= max && max > -1) {
                        return true;
                    };
                    peer.reset();
                } else if (peer.terminated()) {
                    // no more food, do not increment current for unterminated
                    current ++;
                }
                accept = true;
            }
            
            return true;
        }

        public Enumeration possibilities() {            
            if (terminated() == false) {
                // we force peer reinitialization
                if (peer.terminated()) peer.reset();
                return peer.possibilities();
            } else {
                return org.openide.util.Enumerations.empty();
            }
        }

        protected boolean terminated() {
            if (current != max) return false;
            return peer.terminated();
        }

        protected boolean isOptional() {
            if (min <= current) return true;
            return peer.isOptional();
        }
        
        public String toString() {
            return "MultiplicityGroup[peer=" + peer + ", min=" + min + ", max=" + max + ", current=" + current + "]";
        }
    }

    /**
     * At least one sub-content model must eat.     
     */
    private static class Choice extends ContentModel {

        private ContentModel[] models;
        private boolean modelsThatNotAcceptedAtLeastOne[];

        private boolean terminated = false;

        // index of current model to use <0, models.length>
        private int current = 0;

        public Choice(ContentModel[] models) {
            this.models = models;
            modelsThatNotAcceptedAtLeastOne = new boolean[models.length];
        }


        /**
         * Reset all models upto (inclusive) current one.
         */
        protected void reset() {
            for (int i = 0; ihasNext returns null
         */
        public String next() {
            if (hasNext() == false) {
                throw new IllegalStateException();
            } else {
                String next  = (String) list.get(current);
                current++;
                return next;
            }
        }
        
        /** 
         * @return true if it is assured that next is available. 
         */
        public boolean hasNext() {
            if (list.size() > current) return true;
            if (en.hasMoreElements()) {
                String next = (String) en.nextElement();
                return list.add(next);                
            } else {
                return false;
            }
        }
    }
    
    /**
     * Partial implementation of single-pushback tokenizer.
     */
    private static class PushbackStringTokenizer extends StringTokenizer {
        
        private String pushback = null;
        
        public PushbackStringTokenizer(String tokens, String delim, boolean inc) {
            super(tokens, delim, inc);
        }
        
        public String nextToken() {
            String next;
            if (pushback != null) {
                next = pushback;
                pushback = null;
            } else {
                next = super.nextToken();
            }            
            return next;
        }
        
        public boolean hasMoreTokens() {
            if (pushback != null) {
                return true;
            } else {
                return super.hasMoreTokens();
            }
        }
        
        public void pushback(String pushback) {
            if (this.pushback != null) throw new IllegalStateException();
            this.pushback = pushback;
        }
    }
    
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.