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.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 ExecutionContext originalContext;
350            private final Problems problems;
351            private final String resourceName;
352    
353            protected ImportContext( ExecutionContext context,
354                                     Problems problems,
355                                     String resourceName ) {
356                // Create a context that has a local namespace registry
357                NamespaceRegistry localNamespaces = new LocalNamespaceRegistry(context.getNamespaceRegistry());
358                this.originalContext = context;
359                this.context = context.with(localNamespaces);
360                this.problems = problems;
361                this.resourceName = resourceName;
362            }
363    
364            protected ExecutionContext context() {
365                return this.context;
366            }
367    
368            protected void register( String prefix,
369                                     String uri ) {
370                // Register it in the local registry with the supplied prefix ...
371                context.getNamespaceRegistry().register(prefix, uri);
372    
373                // See if it is already registered in the original context ...
374                NamespaceRegistry registry = originalContext.getNamespaceRegistry();
375                if (!registry.isRegisteredNamespaceUri(uri)) {
376                    // It is not, so register it ...
377                    registry.register(prefix, uri);
378                }
379            }
380    
381            protected NameFactory nameFactory() {
382                return this.context.getValueFactories().getNameFactory();
383            }
384    
385            protected PathFactory pathFactory() {
386                return this.context.getValueFactories().getPathFactory();
387            }
388    
389            protected ValueFactory<String> stringFactory() {
390                return this.context.getValueFactories().getStringFactory();
391            }
392    
393            protected ValueFactory<Boolean> booleanFactory() {
394                return this.context.getValueFactories().getBooleanFactory();
395            }
396    
397            protected void recordError( CommonTree node,
398                                        I18n msg,
399                                        Object... params ) {
400                String location = CndI18n.locationFromLineNumberAndCharacter.text(node.getLine(), node.getCharPositionInLine());
401                problems.addError(msg, resourceName, location, params);
402            }
403    
404            protected void recordError( Throwable throwable,
405                                        CommonTree node,
406                                        I18n msg,
407                                        Object... params ) {
408                String location = CndI18n.locationFromLineNumberAndCharacter.text(node.getLine(), node.getCharPositionInLine());
409                problems.addError(throwable, msg, resourceName, location, params);
410            }
411    
412            protected Name nameFrom( CommonTree node,
413                                     int childType ) {
414                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
415                if (childNode != null && childNode.getChildCount() > 0) {
416                    CommonTree textNode = (CommonTree)childNode.getChild(0);
417                    if (textNode.getToken().getTokenIndex() < 0) return null;
418                    String text = removeQuotes(childNode.getChild(0).getText());
419                    try {
420                        return nameFactory().create(text);
421                    } catch (ValueFormatException e) {
422                        recordError(e, node, CndI18n.expectedValidNameLiteral, text);
423                    }
424                }
425                return null;
426            }
427    
428            protected Name[] namesFrom( CommonTree node,
429                                        int childType ) {
430                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
431                if (childNode != null && childNode.getChildCount() > 0) {
432                    List<Name> names = new ArrayList<Name>();
433                    for (int i = 0; i != childNode.getChildCount(); ++i) {
434                        String text = removeQuotes(childNode.getChild(i).getText());
435                        try {
436                            names.add(nameFactory().create(text));
437                        } catch (ValueFormatException e) {
438                            recordError(e, node, CndI18n.expectedValidNameLiteral, text);
439                        }
440                    }
441                    return names.toArray(new Name[names.size()]);
442                }
443                return new Name[] {};
444            }
445    
446            protected String stringFrom( CommonTree node,
447                                         int childType ) {
448                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
449                if (childNode != null && childNode.getChildCount() > 0) {
450                    String text = removeQuotes(childNode.getChild(0).getText().trim());
451                    try {
452                        return stringFactory().create(text);
453                    } catch (ValueFormatException e) {
454                        recordError(e, node, CndI18n.expectedStringLiteral, text);
455                    }
456                }
457                return null;
458            }
459    
460            protected String[] stringsFrom( CommonTree node,
461                                            int childType ) {
462                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
463                if (childNode != null && childNode.getChildCount() > 0) {
464                    List<String> names = new ArrayList<String>();
465                    for (int i = 0; i != childNode.getChildCount(); ++i) {
466                        String text = removeQuotes(childNode.getChild(i).getText().trim());
467                        try {
468                            names.add(stringFactory().create(text));
469                        } catch (ValueFormatException e) {
470                            recordError(e, node, CndI18n.expectedStringLiteral, text);
471                        }
472                    }
473                    return names.toArray(new String[names.size()]);
474                }
475                return new String[] {};
476            }
477    
478            protected boolean booleanFrom( CommonTree node,
479                                           int childType,
480                                           boolean defaultValue ) {
481                CommonTree childNode = (CommonTree)node.getFirstChildWithType(childType);
482                if (childNode != null && childNode.getChildCount() > 0) {
483                    String text = removeQuotes(childNode.getChild(0).getText());
484                    try {
485                        return booleanFactory().create(text);
486                    } catch (ValueFormatException e) {
487                        recordError(e, node, CndI18n.expectedBooleanLiteral, text);
488                    }
489                }
490                return defaultValue;
491            }
492    
493            protected QueryOperator[] queryOperatorsFrom( CommonTree node,
494                                                          int childType ) {
495                String text = stringFrom(node, childType);
496                if (text != null) {
497                    String[] literals = text.split(",");
498                    if (literals.length != 0) {
499                        Set<QueryOperator> operators = new HashSet<QueryOperator>();
500                        for (String literal : literals) {
501                            literal = literal.trim();
502                            if (literal.length() == 0) continue;
503                            QueryOperator operator = QueryOperator.forText(literal);
504                            if (operator != null) {
505                                operators.add(operator);
506                            } else {
507                                recordError(node, CndI18n.expectedValidQueryOperator, literal);
508                            }
509                        }
510                        return operators.toArray(new QueryOperator[operators.size()]);
511                    }
512                }
513                return new QueryOperator[] {};
514            }
515    
516            protected String propertyTypeNameFrom( CommonTree node,
517                                                   int childType ) {
518                String text = stringFrom(node, childType);
519                if ("*".equals(text)) text = "undefined";
520                String upperText = text.toUpperCase();
521                if (!VALID_PROPERTY_TYPES.contains(upperText)) {
522                    recordError(node, CndI18n.expectedValidPropertyTypeName, text, VALID_PROPERTY_TYPES);
523                    return null;
524                }
525                return upperText;
526            }
527    
528            protected String onParentVersionFrom( CommonTree node,
529                                                  int childType ) {
530                String text = stringFrom(node, childType);
531                if (text == null) return "COPY";
532                String upperText = text.toUpperCase();
533                if (!VALID_ON_PARENT_VERSION.contains(upperText)) {
534                    recordError(node, CndI18n.expectedValidOnParentVersion, text, VALID_ON_PARENT_VERSION);
535                    return null;
536                }
537                return upperText;
538            }
539    
540            protected Path createNodeType( CommonTree nodeType,
541                                           Path parentPath ) {
542                Name name = nameFrom(nodeType, CndLexer.NAME);
543                Name[] supertypes = namesFrom(nodeType, CndLexer.SUPERTYPES);
544                boolean isAbstract = booleanFrom(nodeType, CndLexer.IS_ABSTRACT, false);
545                boolean hasOrderableChildNodes = booleanFrom(nodeType, CndLexer.HAS_ORDERABLE_CHILD_NODES, false);
546                boolean isMixin = booleanFrom(nodeType, CndLexer.IS_MIXIN, false);
547                boolean isQueryable = booleanFrom(nodeType, CndLexer.IS_QUERYABLE, true);
548                Name primaryItemName = nameFrom(nodeType, CndLexer.PRIMARY_ITEM_NAME);
549    
550                if (primaryItemName == null) {
551                    // See if one of the property definitions is marked as the primary ...
552                    CommonTree propertyDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.PROPERTY_DEFINITION);
553                    if (propertyDefinitions != null) {
554                        // Walk each of the nodes under PROPERTY_DEFINITION ...
555                        for (int j = 0; j != propertyDefinitions.getChildCount(); ++j) {
556                            CommonTree propDefn = (CommonTree)propertyDefinitions.getChild(j);
557                            if (booleanFrom(propDefn, CndLexer.IS_PRIMARY_PROPERTY, false)) {
558                                primaryItemName = nameFrom(propDefn, CndLexer.NAME);
559                                break;
560                            }
561                        }
562                    }
563                }
564                if (primaryItemName == null) {
565                    // See if one of the child definitions is marked as the primary ...
566                    CommonTree childNodeDefinitions = (CommonTree)nodeType.getFirstChildWithType(CndLexer.CHILD_NODE_DEFINITION);
567                    if (childNodeDefinitions != null) {
568                        // Walk each of the nodes under CHILD_NODE_DEFINITION ...
569                        for (int j = 0; j != childNodeDefinitions.getChildCount(); ++j) {
570                            CommonTree childDefn = (CommonTree)childNodeDefinitions.getChild(j);
571                            if (booleanFrom(childDefn, CndLexer.IS_PRIMARY_PROPERTY, false)) {
572                                primaryItemName = nameFrom(childDefn, CndLexer.NAME);
573                                break;
574                            }
575                        }
576                    }
577                }
578    
579                // Create the node for the node type ...
580                if (name == null) return null;
581                Path path = pathFactory().create(parentPath, name);
582    
583                PropertyFactory factory = context.getPropertyFactory();
584                destination.create(path,
585                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE),
586                                   factory.create(JcrLexicon.SUPERTYPES, (Object[])supertypes),
587                                   factory.create(JcrLexicon.IS_ABSTRACT, isAbstract),
588                                   factory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, hasOrderableChildNodes),
589                                   factory.create(JcrLexicon.IS_MIXIN, isMixin),
590                                   factory.create(JcrLexicon.IS_QUERYABLE, isQueryable),
591                                   factory.create(JcrLexicon.NODE_TYPE_NAME, name),
592                                   factory.create(JcrLexicon.PRIMARY_ITEM_NAME, primaryItemName));
593    
594                return path;
595            }
596    
597            protected Path createPropertyDefinition( CommonTree propDefn,
598                                                     Path parentPath ) {
599                Name name = nameFrom(propDefn, CndLexer.NAME);
600                String requiredType = propertyTypeNameFrom(propDefn, CndLexer.REQUIRED_TYPE);
601                String[] defaultValues = stringsFrom(propDefn, CndLexer.DEFAULT_VALUES);
602                boolean multiple = booleanFrom(propDefn, CndLexer.MULTIPLE, false);
603                boolean mandatory = booleanFrom(propDefn, CndLexer.MANDATORY, false);
604                boolean autoCreated = booleanFrom(propDefn, CndLexer.AUTO_CREATED, false);
605                boolean isProtected = booleanFrom(propDefn, CndLexer.PROTECTED, false);
606                String onParentVersion = onParentVersionFrom(propDefn, CndLexer.ON_PARENT_VERSION);
607                /*QueryOperator[] queryOperators =*/queryOperatorsFrom(propDefn, CndLexer.QUERY_OPERATORS);
608                boolean isFullTextSearchable = booleanFrom(propDefn, CndLexer.IS_FULL_TEXT_SEARCHABLE, true);
609                boolean isQueryOrderable = booleanFrom(propDefn, CndLexer.IS_QUERY_ORDERERABLE, true);
610                String[] valueConstraints = stringsFrom(propDefn, CndLexer.VALUE_CONSTRAINTS);
611    
612                // Create the node for the node type ...
613                if (name == null) return null;
614                Path path = pathFactory().create(parentPath, JcrLexicon.PROPERTY_DEFINITION);
615    
616                PropertyFactory factory = context.getPropertyFactory();
617                destination.create(path,
618                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION),
619                                   factory.create(JcrLexicon.REQUIRED_TYPE, requiredType),
620                                   factory.create(JcrLexicon.DEFAULT_VALUES, (Object[])defaultValues),
621                                   factory.create(JcrLexicon.MULTIPLE, multiple),
622                                   factory.create(JcrLexicon.MANDATORY, mandatory),
623                                   factory.create(JcrLexicon.NAME, name),
624                                   factory.create(JcrLexicon.AUTO_CREATED, autoCreated),
625                                   factory.create(JcrLexicon.PROTECTED, isProtected),
626                                   factory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion),
627                                   // factory.create(DnaLexicon.QUERY_OPERATORS, queryOperators),
628                                   factory.create(JcrLexicon.IS_FULL_TEXT_SEARCHABLE, isFullTextSearchable),
629                                   factory.create(JcrLexicon.IS_QUERY_ORDERABLE, isQueryOrderable),
630                                   factory.create(JcrLexicon.VALUE_CONSTRAINTS, (Object[])valueConstraints));
631    
632                return path;
633            }
634    
635            protected Path createChildDefinition( CommonTree childDefn,
636                                                  Path parentPath ) {
637                Name name = nameFrom(childDefn, CndLexer.NAME);
638                Name[] requiredPrimaryTypes = namesFrom(childDefn, CndLexer.REQUIRED_PRIMARY_TYPES);
639                Name defaultPrimaryType = nameFrom(childDefn, CndLexer.DEFAULT_PRIMARY_TYPE);
640                boolean mandatory = booleanFrom(childDefn, CndLexer.MANDATORY, false);
641                boolean autoCreated = booleanFrom(childDefn, CndLexer.AUTO_CREATED, false);
642                boolean isProtected = booleanFrom(childDefn, CndLexer.PROTECTED, false);
643                String onParentVersion = onParentVersionFrom(childDefn, CndLexer.ON_PARENT_VERSION);
644                boolean sameNameSiblings = booleanFrom(childDefn, CndLexer.SAME_NAME_SIBLINGS, false);
645    
646                // Create the node for the node type ...
647                if (name == null) return null;
648                Path path = pathFactory().create(parentPath, JcrLexicon.CHILD_NODE_DEFINITION);
649    
650                PropertyFactory factory = context.getPropertyFactory();
651                destination.create(path,
652                                   factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION),
653                                   factory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, (Object[])requiredPrimaryTypes),
654                                   factory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, defaultPrimaryType),
655                                   factory.create(JcrLexicon.MANDATORY, mandatory),
656                                   factory.create(JcrLexicon.NAME, name),
657                                   factory.create(JcrLexicon.AUTO_CREATED, autoCreated),
658                                   factory.create(JcrLexicon.PROTECTED, isProtected),
659                                   factory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion),
660                                   factory.create(JcrLexicon.SAME_NAME_SIBLINGS, sameNameSiblings));
661    
662                return path;
663            }
664        }
665    
666        protected class Parser extends CndParser {
667            private final Problems problems;
668            private final String nameOfResource;
669    
670            public Parser( TokenStream input,
671                           Problems problems,
672                           String nameOfResource ) {
673                super(input);
674                this.problems = problems;
675                this.nameOfResource = nameOfResource;
676            }
677    
678            @Override
679            public void displayRecognitionError( String[] tokenNames,
680                                                 RecognitionException e ) {
681                if (problems != null) {
682                    String hdr = getErrorHeader(e);
683                    String msg = getErrorMessage(e, tokenNames);
684                    problems.addError(CndI18n.passthrough, nameOfResource, hdr, msg);
685                } else {
686                    super.displayRecognitionError(tokenNames, e);
687                }
688            }
689        }
690    
691        /**
692         * Specialization of an {@link ANTLRInputStream} that converts all tokens to lowercase, allowing the grammar to be
693         * case-insensitive. See the <a href="http://www.antlr.org/wiki/pages/viewpage.action?pageId=1782">ANTLR documentation</a>.
694         */
695        protected class CaseInsensitiveInputStream extends ANTLRInputStream {
696            protected CaseInsensitiveInputStream( InputStream stream ) throws IOException {
697                super(stream);
698            }
699    
700            /**
701             * {@inheritDoc}
702             * 
703             * @see org.antlr.runtime.ANTLRStringStream#LA(int)
704             */
705            @Override
706            public int LA( int i ) {
707                if (i == 0) {
708                    return 0; // undefined
709                }
710                if (i < 0) {
711                    i++; // e.g., translate LA(-1) to use offset 0
712                }
713    
714                if ((p + i - 1) >= n) {
715                    return CharStream.EOF;
716                }
717                return Character.toLowerCase(data[p + i - 1]);
718            }
719        }
720    
721        /**
722         * Specialization of an {@link ANTLRInputStream} that converts all tokens to lowercase, allowing the grammar to be
723         * case-insensitive. See the <a href="http://www.antlr.org/wiki/pages/viewpage.action?pageId=1782">ANTLR documentation</a>.
724         */
725        protected class CaseInsensitiveFileStream extends ANTLRFileStream {
726            protected CaseInsensitiveFileStream( String fileName ) throws IOException {
727                super(fileName, null);
728            }
729    
730            protected CaseInsensitiveFileStream( String fileName,
731                                                 String encoding ) throws IOException {
732                super(fileName, encoding);
733            }
734    
735            /**
736             * {@inheritDoc}
737             * 
738             * @see org.antlr.runtime.ANTLRStringStream#LA(int)
739             */
740            @Override
741            public int LA( int i ) {
742                if (i == 0) {
743                    return 0; // undefined
744                }
745                if (i < 0) {
746                    i++; // e.g., translate LA(-1) to use offset 0
747                }
748    
749                if ((p + i - 1) >= n) {
750    
751                    return CharStream.EOF;
752                }
753                return Character.toLowerCase(data[p + i - 1]);
754            }
755        }
756    
757    }