001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * Unless otherwise indicated, all code in JBoss DNA is licensed
010     * to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.cnd;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.File;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.util.ArrayList;
031    import java.util.Arrays;
032    import java.util.Collections;
033    import java.util.HashSet;
034    import java.util.List;
035    import java.util.Set;
036    import net.jcip.annotations.NotThreadSafe;
037    import org.antlr.runtime.ANTLRFileStream;
038    import org.antlr.runtime.ANTLRInputStream;
039    import org.antlr.runtime.CharStream;
040    import org.antlr.runtime.CommonTokenStream;
041    import org.antlr.runtime.RecognitionException;
042    import org.antlr.runtime.TokenStream;
043    import org.antlr.runtime.tree.CommonTree;
044    import org.antlr.runtime.tree.RewriteCardinalityException;
045    import org.jboss.dna.common.collection.Problems;
046    import org.jboss.dna.common.i18n.I18n;
047    import org.jboss.dna.common.util.CheckArg;
048    import org.jboss.dna.graph.ExecutionContext;
049    import org.jboss.dna.graph.JcrLexicon;
050    import org.jboss.dna.graph.JcrNtLexicon;
051    import org.jboss.dna.graph.io.Destination;
052    import org.jboss.dna.graph.property.Name;
053    import org.jboss.dna.graph.property.NameFactory;
054    import org.jboss.dna.graph.property.NamespaceRegistry;
055    import org.jboss.dna.graph.property.Path;
056    import org.jboss.dna.graph.property.PathFactory;
057    import org.jboss.dna.graph.property.PropertyFactory;
058    import org.jboss.dna.graph.property.ValueFactory;
059    import org.jboss.dna.graph.property.ValueFormatException;
060    import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry;
061    
062    /**
063     * A class that imports the node types contained in a JCR Compact Node Definition (CND) file into graph content. The content is
064     * written using the graph structured defined by JCR and the "{@code nt:nodeType}", "{@code nt:propertyDefinition}", and "{@code
065     * nt:childNodeDefinition}" node types.
066     * <p>
067     * Although instances of this class never change their behavior and all processing is done in local contexts, {@link Destination}
068     * is not thread-safe and therefore this component may not be considered thread-safe.
069     * </p>
070     */
071    @NotThreadSafe
072    public class CndImporter {
073    
074        private static final Set<String> VALID_PROPERTY_TYPES = Collections.unmodifiableSet(new HashSet<String>(
075                                                                                                                Arrays.asList(new String[] {
076                                                                                                                    "STRING",
077                                                                                                                    "BINARY", "LONG",
078                                                                                                                    "DOUBLE",
079                                                                                                                    "BOOLEAN",
080                                                                                                                    "DECIMAL",
081                                                                                                                    "DATE", "NAME",
082                                                                                                                    "PATH",
083                                                                                                                    "REFERENCE",
084                                                                                                                    "WEAKREFERENCE",
085                                                                                                                    "URI",
086                                                                                                                    "UNDEFINED"})));
087        private static final Set<String> VALID_ON_PARENT_VERSION = Collections.unmodifiableSet(new HashSet<String>(
088                                                                                                                   Arrays.asList(new String[] {
089                                                                                                                       "COPY",
090                                                                                                                       "VERSION",
091                                                                                                                       "INITIALIZE",
092                                                                                                                       "COMPUTE",
093                                                                                                                       "IGNORE",
094                                                                                                                       "ABORT"})));
095        protected final Destination destination;
096        protected final Path parentPath;
097        private boolean debug = false;
098    
099        /**
100         * Create a new importer that will place the content in the supplied destination under the supplied path.
101         * 
102         * @param destination the destination where content is to be written
103         * @param parentPath the path in the destination below which the generated content is to appear
104         * @throws IllegalArgumentException if either parameter is null
105         */
106        public CndImporter( Destination destination,
107                            Path parentPath ) {
108            CheckArg.isNotNull(destination, "destination");
109            CheckArg.isNotNull(parentPath, "parentPath");
110            this.destination = destination;
111            this.parentPath = parentPath;
112        }
113    
114        void setDebug( boolean value ) {
115            this.debug = value;
116        }
117    
118        protected ExecutionContext context() {
119            return this.destination.getExecutionContext();
120        }
121    
122        protected NamespaceRegistry namespaces() {
123            return context().getNamespaceRegistry();
124        }
125    
126        protected NameFactory nameFactory() {
127            return context().getValueFactories().getNameFactory();
128        }
129    
130        protected ValueFactory<String> stringFactory() {
131            return context().getValueFactories().getStringFactory();
132        }
133    
134        protected ValueFactory<Boolean> booleanFactory() {
135            return context().getValueFactories().getBooleanFactory();
136        }
137    
138        /**
139         * Import the CND content from the supplied stream, placing the content into the importer's destination.
140         * 
141         * @param stream the stream containing the CND content
142         * @param problems where any problems encountered during import should be reported
143         * @param resourceName a logical name for the resource name to be used when reporting problems; may be null if there is no
144         *        useful name
145         * @throws IOException if there is a problem reading from the supplied stream
146         */
147        public void importFrom( InputStream stream,
148                                Problems problems,
149                                String resourceName ) throws IOException {
150            CndLexer lex = new CndLexer(new CaseInsensitiveInputStream(stream));
151            importFrom(lex, resourceName, problems);
152        }
153    
154        /**
155         * Import the CND content from the supplied stream, placing the content into the importer's destination.
156         * 
157         * @param content the string containing the CND content
158         * @param problems where any problems encountered during import should be reported
159         * @param resourceName a logical name for the resource name to be used when reporting problems; may be null if there is no
160         *        useful name
161         * @throws IOException if there is a problem reading from the supplied stream
162         */
163        public void importFrom( String content,
164                                Problems problems,
165                                String resourceName ) throws IOException {
166            ByteArrayInputStream stream = new ByteArrayInputStream(content.getBytes());
167            importFrom(stream, problems, resourceName);
168        }
169    
170        /**
171         * Import the CND content from the supplied stream, placing the content into the importer's destination.
172         * 
173         * @param file the file containing the CND content
174         * @param problems where any problems encountered during import should be reported
175         * @throws IOException if there is a problem reading from the supplied stream
176         */
177        public void importFrom( File file,
178                                Problems problems ) throws IOException {
179            CndLexer lex = new CndLexer(new CaseInsensitiveFileStream(file.getAbsolutePath()));
180            importFrom(lex, file.getCanonicalPath(), problems);
181        }
182    
183        protected void importFrom( CndLexer lexer,
184                                   String resourceName,
185                                   Problems problems ) {
186            CommonTokenStream tokens = new CommonTokenStream(lexer);
187            CndParser parser = new Parser(tokens, problems, resourceName);
188    
189            // Create a new context with our own namespace registry ...
190            ImportContext context = new ImportContext(context(), problems, resourceName);
191            CommonTree ast = null;
192            try {
193                ast = (CommonTree)parser.cnd().getTree();
194            } catch (RecognitionException e) {
195                // already handled by Parser, so we should not handle twice
196            } catch (RewriteCardinalityException e) {
197                // already handled by Parser, so we should not handle twice
198            } catch (RuntimeException e) {
199                problems.addError(e, CndI18n.errorImportingCndContent, (Object)resourceName, e.getMessage());
200            }
201    
202            if (ast != null && problems.isEmpty()) {
203    
204                // --------------
205                // Namespaces ...
206                // --------------
207    
208                /* 
209                  NAMESPACES
210                   +- NODE (multiple)
211                       +- PREFIX
212                           +- string value
213                       +- URI
214                           +- string value
215                 */
216    
217                // Get the namespaces before we do anything else ...
218                CommonTree namespaces = (CommonTree)ast.getFirstChildWithType(CndLexer.NAMESPACES);
219                if (namespaces != null) {
220                    for (int i = 0; i != namespaces.getChildCount(); ++i) {
221                        CommonTree namespace = (CommonTree)namespaces.getChild(i);
222                        String prefix = namespace.getFirstChildWithType(CndLexer.PREFIX).getChild(0).getText();
223                        String uri = namespace.getFirstChildWithType(CndLexer.URI).getChild(0).getText();
224                        // Register the namespace ...
225                        context.namespaces().register(removeQuotes(prefix), removeQuotes(uri));
226                    }
227                }
228    
229                // --------------
230                // Node Types ...
231                // --------------
232    
233                /*
234                NODE_TYPES
235                 +- NODE (multiple)
236                     +- NAME                                 [nt:nodeType/@jcr:name]
237                         +- string value
238                     +- PRIMARY_TYPE                         [nt:base/@jcr:primaryType]
239                         +- string with value 'nt:nodeType'
240                     +- SUPERTYPES                           [nt:nodeType/@jcr:supertypes]
241                         +- string value(s)
242                     +- IS_ABSTRACT                          [nt:nodeType/@jcr:isAbstract]
243                         +- string containing boolean value (or false if not present)
244                     +- HAS_ORDERABLE_CHILD_NODES            [nt:nodeType/@jcr:hasOrderableChildNodes]
245                         +- string containing boolean value (or false if not present)
246                     +- IS_MIXIN                             [nt:nodeType/@jcr:isMixin]
247                         +- string containing boolean value (or false if not present)
248                     +- IS_QUERYABLE                         [nt:nodeType/@jcr:isQueryable]
249                         +- string containing boolean value (or true if not present)
250                     +- PRIMARY_ITEM_NAME                    [nt:nodeType/@jcr:primaryItemName]
251                         +- string containing string value
252                     +- PROPERTY_DEFINITION                  [nt:nodeType/@jcr:propertyDefinition]
253                         +- NODE (multiple)
254                             +- NAME                         [nt:propertyDefinition/@jcr:name]
255                                 +- string value
256                             +- PRIMARY_TYPE                 [nt:base/@jcr:primaryType]
257                                 +- string with value 'nt:propertyDefinition'
258                             +- REQUIRED_TYPE                [nt:propertyDefinition/@jcr:propertyType]
259                                 +- string value (limited to one of the predefined types)
260                             +- DEFAULT_VALUES               [nt:propertyDefinition/@jcr:defaultValues]
261                                 +- string value(s)
262                             +- MULTIPLE                     [nt:propertyDefinition/@jcr:multiple]
263                                 +- string containing boolean value (or false if not present)
264                             +- MANDATORY                    [nt:propertyDefinition/@jcr:mandatory]
265                                 +- string containing boolean value (or false if not present)
266                             +- AUTO_CREATED                 [nt:propertyDefinition/@jcr:autoCreated]
267                                 +- string containing boolean value (or false if not present)
268                             +- PROTECTED                    [nt:propertyDefinition/@jcr:protected]
269                                 +- string containing boolean value (or false if not present)
270                             +- ON_PARENT_VERSION            [nt:propertyDefinition/@jcr:onParentVersion]
271                                 +- string value (limited to one of the predefined literal values)
272                             +- QUERY_OPERATORS              
273                                 +- string value (containing a comma-separated list of operator literals)
274                             +- IS_FULL_TEXT_SEARCHABLE      [nt:propertyDefinition/@jcr:isFullTextSearchable]
275                                 +- string containing boolean value (or true if not present)
276                             +- IS_QUERY_ORDERABLE           [nt:propertyDefinition/@jcr:isQueryOrderable]
277                                 +- string containing boolean value (or true if not present)
278                             +- VALUE_CONSTRAINTS            [nt:propertyDefinition/@jcr:valueConstraints]
279                                 +- string value(s)
280                     +- CHILD_NODE_DEFINITION                [nt:nodeType/@jcr:childNodeDefinition]
281                         +- NODE (multiple)
282                             +- NAME                         [nt:childNodeDefinition/@jcr:name]
283                                 +- string value
284                             +- PRIMARY_TYPE                 [nt:base/@jcr:primaryType]
285                                 +- string with value 'nt:childNodeDefinition'
286                             +- REQUIRED_PRIMARY_TYPES       [nt:childNodeDefinition/@jcr:requiredPrimaryTypes]
287                                 +- string values (limited to names)
288                             +- DEFAULT_PRIMARY_TYPE         [nt:childNodeDefinition/@jcr:defaultPrimaryType]
289                                 +- string value (limited to a name)
290                             +- MANDATORY                    [nt:childNodeDefinition/@jcr:mandatory]
291                                 +- string containing boolean value (or false if not present)
292                             +- AUTO_CREATED                 [nt:childNodeDefinition/@jcr:autoCreated]
293                                 +- string containing boolean value (or false if not present)
294                             +- PROTECTED                    [nt:childNodeDefinition/@jcr:protected]
295                                 +- string containing boolean value (or false if not present)
296                             +- SAME_NAME_SIBLINGS           [nt:childNodeDefinition/@jcr:sameNameSiblings]
297                                 +- string containing boolean value (or false if not present)
298                             +- ON_PARENT_VERSION            [nt:childNodeDefinition/@jcr:onParentVersion]
299                                 +- string value (limited to one of the predefined literal values)
300                */
301    
302                // Get the node types ...
303                CommonTree nodeTypes = (CommonTree)ast.getFirstChildWithType(CndLexer.NODE_TYPES);
304                if (nodeTypes != null) {
305                    int numNodeTypes = 0;
306                    // Walk each of the nodes underneath the NODE_TYPES parent node ...
307                    for (int i = 0; i != nodeTypes.getChildCount(); ++i) {
308                        CommonTree nodeType = (CommonTree)nodeTypes.getChild(i);
309                        if (this.debug) System.out.println(nodeType.toStringTree());
310                        Path nodeTypePath = context.createNodeType(nodeType, parentPath);
311                        if (nodeTypePath == null) continue;
312                        ++numNodeTypes;
313    
314                        CommonTree propertyDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.PROPERTY_DEFINITION);
315                        if (propertyDefinitions != null) {
316                            // Walk each of the nodes under PROPERTY_DEFINITION ...
317                            for (int j = 0; j != propertyDefinitions.getChildCount(); ++j) {
318                                CommonTree propDefn = (CommonTree)propertyDefinitions.getChild(j);
319                                context.createPropertyDefinition(propDefn, nodeTypePath);
320                            }
321                        }
322    
323                        CommonTree childNodeDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.CHILD_NODE_DEFINITION);
324                        if (childNodeDefinitions != null) {
325                            // Walk each of the nodes under CHILD_NODE_DEFINITION ...
326                            for (int j = 0; j != childNodeDefinitions.getChildCount(); ++j) {
327                                CommonTree childDefn = (CommonTree)childNodeDefinitions.getChild(j);
328                                context.createChildDefinition(childDefn, nodeTypePath);
329                            }
330                        }
331                    }
332    
333                    // Submit the destination
334                    destination.submit();
335                }
336            }
337        }
338    
339        protected final String removeQuotes( String text ) {
340            // Remove leading and trailing quotes, if there are any ...
341            return text.replaceFirst("^['\"]+", "").replaceAll("['\"]+$", "");
342        }
343    
344        /**
345         * Utility class that uses a context with a local namespace registry, along with the problems and resource name.
346         */
347        protected final class ImportContext {
348            private final ExecutionContext context;
349            private final Problems problems;
350            private final String resourceName;
351    
352            protected ImportContext( ExecutionContext context,
353                                     Problems problems,
354                                     String resourceName ) {
355                // Create a context that has a local namespace registry
356                NamespaceRegistry localNamespaces = new LocalNamespaceRegistry(context.getNamespaceRegistry());
357                this.context = context.with(localNamespaces);
358                this.problems = problems;
359                this.resourceName = resourceName;
360            }
361    
362            protected ExecutionContext context() {
363                return this.context;
364            }
365    
366            protected NamespaceRegistry namespaces() {
367                return this.context.getNamespaceRegistry();
368            }
369    
370            protected NameFactory nameFactory() {
371                return this.context.getValueFactories().getNameFactory();
372            }
373    
374            protected PathFactory pathFactory() {
375                return this.context.getValueFactories().getPathFactory();
376            }
377    
378            protected ValueFactory<String> stringFactory() {
379                return this.context.getValueFactories().getStringFactory();
380            }
381    
382            protected ValueFactory<Boolean> booleanFactory() {
383                return this.context.getValueFactories().getBooleanFactory();
384            }
385    
386            protected void recordError( CommonTree node,
387                                        I18n msg,
388                                        Object... params ) {
389                String location = CndI18n.locationFromLineNumberAndCharacter.text(node.getLine(), node.getCharPositionInLine());
390                problems.addError(msg, resourceName, location, params);
391            }
392    
393            protected void recordError( Throwable throwable,
394                                        CommonTree node,
395                                        I18n msg,
396                                        Object... params ) {
397                String location = CndI18n.locationFromLineNumberAndCharacter.text(node.getLine(), node.getCharPositionInLine());
398                problems.addError(throwable, msg, resourceName, location, params);
399            }
400    
401            protected Name nameFrom( CommonTree node,
402                                     int childType ) {
403                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
404                if (childNode != null && childNode.getChildCount() > 0) {
405                    CommonTree textNode = (CommonTree)childNode.getChild(0);
406                    if (textNode.getToken().getTokenIndex() < 0) return null;
407                    String text = removeQuotes(childNode.getChild(0).getText());
408                    try {
409                        return nameFactory().create(text);
410                    } catch (ValueFormatException e) {
411                        recordError(e, node, CndI18n.expectedValidNameLiteral, text);
412                    }
413                }
414                return null;
415            }
416    
417            protected Name[] namesFrom( CommonTree node,
418                                        int childType ) {
419                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
420                if (childNode != null && childNode.getChildCount() > 0) {
421                    List<Name> names = new ArrayList<Name>();
422                    for (int i = 0; i != childNode.getChildCount(); ++i) {
423                        String text = removeQuotes(childNode.getChild(i).getText());
424                        try {
425                            names.add(nameFactory().create(text));
426                        } catch (ValueFormatException e) {
427                            recordError(e, node, CndI18n.expectedValidNameLiteral, text);
428                        }
429                    }
430                    return names.toArray(new Name[names.size()]);
431                }
432                return new Name[] {};
433            }
434    
435            protected String stringFrom( CommonTree node,
436                                         int childType ) {
437                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
438                if (childNode != null && childNode.getChildCount() > 0) {
439                    String text = removeQuotes(childNode.getChild(0).getText().trim());
440                    try {
441                        return stringFactory().create(text);
442                    } catch (ValueFormatException e) {
443                        recordError(e, node, CndI18n.expectedStringLiteral, text);
444                    }
445                }
446                return null;
447            }
448    
449            protected String[] stringsFrom( CommonTree node,
450                                            int childType ) {
451                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
452                if (childNode != null && childNode.getChildCount() > 0) {
453                    List<String> names = new ArrayList<String>();
454                    for (int i = 0; i != childNode.getChildCount(); ++i) {
455                        String text = removeQuotes(childNode.getChild(i).getText().trim());
456                        try {
457                            names.add(stringFactory().create(text));
458                        } catch (ValueFormatException e) {
459                            recordError(e, node, CndI18n.expectedStringLiteral, text);
460                        }
461                    }
462                    return names.toArray(new String[names.size()]);
463                }
464                return new String[] {};
465            }
466    
467            protected boolean booleanFrom( CommonTree node,
468                                           int childType,
469                                           boolean defaultValue ) {
470                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
471                if (childNode != null && childNode.getChildCount() > 0) {
472                    String text = removeQuotes(childNode.getChild(0).getText());
473                    try {
474                        return booleanFactory().create(text);
475                    } catch (ValueFormatException e) {
476                        recordError(e, node, CndI18n.expectedBooleanLiteral, text);
477                    }
478                }
479                return defaultValue;
480            }
481    
482            protected QueryOperator[] queryOperatorsFrom( CommonTree node,
483                                                          int childType ) {
484                String text = stringFrom(node, childType);
485                if (text != null) {
486                    String[] literals = text.split(",");
487                    if (literals.length != 0) {
488                        Set<QueryOperator> operators = new HashSet<QueryOperator>();
489                        for (String literal : literals) {
490                            literal = literal.trim();
491                            if (literal.length() == 0) continue;
492                            QueryOperator operator = QueryOperator.forText(literal);
493                            if (operator != null) {
494                                operators.add(operator);
495                            } else {
496                                recordError(node, CndI18n.expectedValidQueryOperator, literal);
497                            }
498                        }
499                        return operators.toArray(new QueryOperator[operators.size()]);
500                    }
501                }
502                return new QueryOperator[] {};
503            }
504    
505            protected String propertyTypeNameFrom( CommonTree node,
506                                                   int childType ) {
507                String text = stringFrom(node, childType);
508                if (text.equals("*")) text = "undefined";
509                String upperText = text.toUpperCase();
510                if (!VALID_PROPERTY_TYPES.contains(upperText)) {
511                    recordError(node, CndI18n.expectedValidPropertyTypeName, text, VALID_PROPERTY_TYPES);
512                    return null;
513                }
514                return upperText;
515            }
516    
517            protected String onParentVersionFrom( CommonTree node,
518                                                  int childType ) {
519                String text = stringFrom(node, childType);
520                if (text == null) return "COPY";
521                String upperText = text.toUpperCase();
522                if (!VALID_ON_PARENT_VERSION.contains(upperText)) {
523                    recordError(node, CndI18n.expectedValidOnParentVersion, text, VALID_ON_PARENT_VERSION);
524                    return null;
525                }
526                return upperText;
527            }
528    
529            protected Path createNodeType( CommonTree nodeType,
530                                           Path parentPath ) {
531                Name name = nameFrom(nodeType, CndLexer.NAME);
532                Name[] supertypes = namesFrom(nodeType, CndLexer.SUPERTYPES);
533                boolean isAbstract = booleanFrom(nodeType, CndLexer.IS_ABSTRACT, false);
534                boolean hasOrderableChildNodes = booleanFrom(nodeType, CndLexer.HAS_ORDERABLE_CHILD_NODES, false);
535                boolean isMixin = booleanFrom(nodeType, CndLexer.IS_MIXIN, false);
536                boolean isQueryable = booleanFrom(nodeType, CndLexer.IS_QUERYABLE, true);
537                Name primaryItemName = nameFrom(nodeType, CndLexer.PRIMARY_ITEM_NAME);
538    
539                if (primaryItemName == null) {
540                    // See if one of the property definitions is marked as the primary ...
541                    CommonTree propertyDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.PROPERTY_DEFINITION);
542                    if (propertyDefinitions != null) {
543                        // Walk each of the nodes under PROPERTY_DEFINITION ...
544                        for (int j = 0; j != propertyDefinitions.getChildCount(); ++j) {
545                            CommonTree propDefn = (CommonTree)propertyDefinitions.getChild(j);
546                            if (booleanFrom(propDefn, CndLexer.IS_PRIMARY_PROPERTY, false)) {
547                                primaryItemName = nameFrom(propDefn, CndLexer.NAME);
548                                break;
549                            }
550                        }
551                    }
552                }
553                if (primaryItemName == null) {
554                    // See if one of the child definitions is marked as the primary ...
555                    CommonTree childNodeDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.CHILD_NODE_DEFINITION);
556                    if (childNodeDefinitions != null) {
557                        // Walk each of the nodes under CHILD_NODE_DEFINITION ...
558                        for (int j = 0; j != childNodeDefinitions.getChildCount(); ++j) {
559                            CommonTree childDefn = (CommonTree)childNodeDefinitions.getChild(j);
560                            if (booleanFrom(childDefn, CndLexer.IS_PRIMARY_PROPERTY, false)) {
561                                primaryItemName = nameFrom(childDefn, CndLexer.NAME);
562                                break;
563                            }
564                        }
565                    }
566                }
567    
568                // Create the node for the node type ...
569                if (name == null) return null;
570                Path path = pathFactory().create(parentPath, name);
571    
572                PropertyFactory factory = context.getPropertyFactory();
573                destination.create(path,
574                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE),
575                                   factory.create(JcrLexicon.SUPERTYPES, (Object[])supertypes),
576                                   factory.create(JcrLexicon.IS_ABSTRACT, isAbstract),
577                                   factory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, hasOrderableChildNodes),
578                                   factory.create(JcrLexicon.IS_MIXIN, isMixin),
579                                   factory.create(JcrLexicon.IS_QUERYABLE, isQueryable),
580                                   factory.create(JcrLexicon.PRIMARY_ITEM_NAME, primaryItemName));
581    
582                return path;
583            }
584    
585            protected Path createPropertyDefinition( CommonTree propDefn,
586                                                     Path parentPath ) {
587                Name name = nameFrom(propDefn, CndLexer.NAME);
588                String requiredType = propertyTypeNameFrom(propDefn, CndLexer.REQUIRED_TYPE);
589                String[] defaultValues = stringsFrom(propDefn, CndLexer.DEFAULT_VALUES);
590                boolean multiple = booleanFrom(propDefn, CndLexer.MULTIPLE, false);
591                boolean mandatory = booleanFrom(propDefn, CndLexer.MANDATORY, false);
592                boolean autoCreated = booleanFrom(propDefn, CndLexer.AUTO_CREATED, false);
593                boolean isProtected = booleanFrom(propDefn, CndLexer.PROTECTED, false);
594                String onParentVersion = onParentVersionFrom(propDefn, CndLexer.ON_PARENT_VERSION);
595                /*QueryOperator[] queryOperators =*/queryOperatorsFrom(propDefn, CndLexer.QUERY_OPERATORS);
596                boolean isFullTextSearchable = booleanFrom(propDefn, CndLexer.IS_FULL_TEXT_SEARCHABLE, true);
597                boolean isQueryOrderable = booleanFrom(propDefn, CndLexer.IS_QUERY_ORDERERABLE, true);
598                String[] valueConstraints = stringsFrom(propDefn, CndLexer.VALUE_CONSTRAINTS);
599    
600                // Create the node for the node type ...
601                if (name == null) return null;
602                Path path = pathFactory().create(parentPath, name);
603    
604                PropertyFactory factory = context.getPropertyFactory();
605                destination.create(path,
606                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION),
607                                   factory.create(JcrLexicon.REQUIRED_TYPE, requiredType),
608                                   factory.create(JcrLexicon.DEFAULT_VALUES, (Object[])defaultValues),
609                                   factory.create(JcrLexicon.MULTIPLE, multiple),
610                                   factory.create(JcrLexicon.MANDATORY, mandatory),
611                                   factory.create(JcrLexicon.AUTO_CREATED, autoCreated),
612                                   factory.create(JcrLexicon.PROTECTED, isProtected),
613                                   factory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion),
614                                   // factory.create(DnaLexicon.QUERY_OPERATORS, queryOperators),
615                                   factory.create(JcrLexicon.IS_FULL_TEXT_SEARCHABLE, isFullTextSearchable),
616                                   factory.create(JcrLexicon.IS_QUERY_ORDERABLE, isQueryOrderable),
617                                   factory.create(JcrLexicon.VALUE_CONSTRAINTS, (Object[])valueConstraints));
618    
619                return path;
620            }
621    
622            protected Path createChildDefinition( CommonTree childDefn,
623                                                  Path parentPath ) {
624                Name name = nameFrom(childDefn, CndLexer.NAME);
625                Name[] requiredPrimaryTypes = namesFrom(childDefn, CndLexer.REQUIRED_PRIMARY_TYPES);
626                Name defaultPrimaryType = nameFrom(childDefn, CndLexer.DEFAULT_PRIMARY_TYPE);
627                boolean mandatory = booleanFrom(childDefn, CndLexer.MANDATORY, false);
628                boolean autoCreated = booleanFrom(childDefn, CndLexer.AUTO_CREATED, false);
629                boolean isProtected = booleanFrom(childDefn, CndLexer.PROTECTED, false);
630                String onParentVersion = onParentVersionFrom(childDefn, CndLexer.ON_PARENT_VERSION);
631                boolean sameNameSiblings = booleanFrom(childDefn, CndLexer.SAME_NAME_SIBLINGS, false);
632    
633                // Create the node for the node type ...
634                if (name == null) return null;
635                Path path = pathFactory().create(parentPath, name);
636    
637                PropertyFactory factory = context.getPropertyFactory();
638                destination.create(path,
639                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION),
640                                   factory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, (Object[])requiredPrimaryTypes),
641                                   factory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, defaultPrimaryType),
642                                   factory.create(JcrLexicon.MANDATORY, mandatory),
643                                   factory.create(JcrLexicon.AUTO_CREATED, autoCreated),
644                                   factory.create(JcrLexicon.PROTECTED, isProtected),
645                                   factory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion),
646                                   factory.create(JcrLexicon.SAME_NAME_SIBLINGS, sameNameSiblings));
647    
648                return path;
649            }
650        }
651    
652        protected class Parser extends CndParser {
653            private final Problems problems;
654            private final String nameOfResource;
655    
656            public Parser( TokenStream input,
657                           Problems problems,
658                           String nameOfResource ) {
659                super(input);
660                this.problems = problems;
661                this.nameOfResource = nameOfResource;
662            }
663    
664            @Override
665            public void displayRecognitionError( String[] tokenNames,
666                                                 RecognitionException e ) {
667                if (problems != null) {
668                    String hdr = getErrorHeader(e);
669                    String msg = getErrorMessage(e, tokenNames);
670                    problems.addError(CndI18n.passthrough, nameOfResource, hdr, msg);
671                } else {
672                    super.displayRecognitionError(tokenNames, e);
673                }
674            }
675        }
676    
677        /**
678         * Specialization of an {@link ANTLRInputStream} that converts all tokens to lowercase, allowing the grammar to be
679         * case-insensitive. See the <a href="http://www.antlr.org/wiki/pages/viewpage.action?pageId=1782">ANTLR documentation</a>.
680         */
681        protected class CaseInsensitiveInputStream extends ANTLRInputStream {
682            protected CaseInsensitiveInputStream( InputStream stream ) throws IOException {
683                super(stream);
684            }
685    
686            /**
687             * {@inheritDoc}
688             * 
689             * @see org.antlr.runtime.ANTLRStringStream#LA(int)
690             */
691            @Override
692            public int LA( int i ) {
693                if (i == 0) {
694                    return 0; // undefined
695                }
696                if (i < 0) {
697                    i++; // e.g., translate LA(-1) to use offset 0
698                }
699    
700                if ((p + i - 1) >= n) {
701                    return CharStream.EOF;
702                }
703                return Character.toLowerCase(data[p + i - 1]);
704            }
705        }
706    
707        /**
708         * Specialization of an {@link ANTLRInputStream} that converts all tokens to lowercase, allowing the grammar to be
709         * case-insensitive. See the <a href="http://www.antlr.org/wiki/pages/viewpage.action?pageId=1782">ANTLR documentation</a>.
710         */
711        protected class CaseInsensitiveFileStream extends ANTLRFileStream {
712            protected CaseInsensitiveFileStream( String fileName ) throws IOException {
713                super(fileName, null);
714            }
715    
716            protected CaseInsensitiveFileStream( String fileName,
717                                                 String encoding ) throws IOException {
718                super(fileName, encoding);
719            }
720    
721            /**
722             * {@inheritDoc}
723             * 
724             * @see org.antlr.runtime.ANTLRStringStream#LA(int)
725             */
726            @Override
727            public int LA( int i ) {
728                if (i == 0) {
729                    return 0; // undefined
730                }
731                if (i < 0) {
732                    i++; // e.g., translate LA(-1) to use offset 0
733                }
734    
735                if ((p + i - 1) >= n) {
736    
737                    return CharStream.EOF;
738                }
739                return Character.toLowerCase(data[p + i - 1]);
740            }
741        }
742    
743    }