CssModel.java :  » IDE-Netbeans » css » org » netbeans » modules » css » editor » model » Java Open Source

Java Open Source » IDE Netbeans » css 
css » org » netbeans » modules » css » editor » model » CssModel.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
package org.netbeans.modules.css.editor.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.modules.gsf.api.CancellableTask;
import org.netbeans.modules.gsf.api.CompilationInfo;
import org.netbeans.modules.gsf.api.Error;
import org.netbeans.modules.gsf.api.ParserResult;
import org.netbeans.modules.gsf.api.Severity;
import org.netbeans.modules.gsf.api.SourceModel;
import org.netbeans.modules.gsf.api.SourceModelFactory;
import org.netbeans.modules.css.gsf.CSSParserResult;
import org.netbeans.modules.css.gsf.EditorAwareSourceTaskSupport;
import org.netbeans.modules.css.parser.ASCII_CharStream;
import org.netbeans.modules.css.parser.CSSParser;
import org.netbeans.modules.css.parser.CSSParserConstants;
import org.netbeans.modules.css.parser.CSSParserTreeConstants;
import org.netbeans.modules.css.parser.NodeVisitor;
import org.netbeans.modules.css.parser.ParseException;
import org.netbeans.modules.css.parser.SimpleNode;
import org.netbeans.modules.css.parser.SimpleNodeUtil;
import org.netbeans.modules.css.parser.Token;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;

/**
 * A domain object model representing CSS file backed up by 
 * instance of {@link javax.swing.text.Document}. For each 
 * document instance there is one instance of the model.
 * 
 * You can get the shared instance by using
 * <code>
 * CssModel.get(yourDocument);
 * </code>

 * The model also allows to listen on the changes of the model.
 *
 * @author Marek Fukala
 */
public final class CssModel {

    private static final Logger LOGGER = Logger.getLogger(CssModel.class.getName());
    private static final boolean LOG = LOGGER.isLoggable(Level.FINE);
    
    /** PropertyChangeEvent of this name is fired when the model 
     * has been successfully updated.
     */
    
    public static final String MODEL_UPDATED = "modelUpdated"; //NOI18N
    /** PropertyChangeEvent of this name is fired when the underlying 
     * document has changed and the model started to reparse it.
     */
    public static final String MODEL_PARSING = "modelParsing"; //NOI18N
    /** PropertyChangeEvent of this name is fired when the underlying 
     * document has changed and contains error which prevents 
     * the parser to successfully finish.
     */
    public static final String MODEL_INVALID = "modelInvalid"; //NOI18N

    /** Gets a shared instance of CssModel for the given document. 
     * 
     * @return instance of {@link CssModel}
     * @param doc source of the model
     */
    public static CssModel get(Document doc) {
        if (doc == null) {
            throw new NullPointerException("Passed null document!");
        }
        CssModel model = (CssModel) doc.getProperty(CssModel.class);
        if (model == null) {
            model = new CssModel(doc);
            doc.putProperty(CssModel.class, model);
        }
        return model;
    }

    /** Winston's workaround. */
    public static CssModel get(InputStream source) {
        try {
            CSSParser parser = new CSSParser();
            parser.ReInit(new ASCII_CharStream(new InputStreamReader(source)));
            SimpleNode node = parser.styleSheet();
            return new CssModel(node);
        } catch (ParseException pe) {
            Exceptions.printStackTrace(pe);
        }
        return null;
    }
    private Document doc;
    private List<CssRule> rules = new ArrayList<CssRule>(10);
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    private boolean immutable;

    private CssModel(Document doc) {
        try {
            this.doc = doc;

            //all domain model objects provided by this model are immutable
            //changes are done by propagating the modifications to the underlying document
            //and subsequent regeneration of the model
            this.immutable = true;

            DataObject od = NbEditorUtilities.getDataObject(doc);
            FileObject fo = od.getPrimaryFile();


            //listen on the css parser
            EditorAwareSourceTaskSupport.Listener parseListener = new EditorAwareSourceTaskSupport.Listener() {

                public void parsed(CompilationInfo info) {
                    try {

                        //ignore if not our document
                        //this is uglyyyyyyyyyyyy!!!!!!!!!! fix the whole support please once you have some time!
                        if (info.getDocument() != CssModel.this.doc) {
                            return;
                        }

                        ParserResult presult = info.getEmbeddedResults("text/x-css").iterator().next();
                        SimpleNode root = ((CSSParserResult) presult).root();


                        if (containsErrors(presult)) {
                            support.firePropertyChange(MODEL_INVALID, rules, null);
                            return;
                        }

                        updateModel(root);
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            };
            EditorAwareSourceTaskSupport.instance().addListener(parseListener);

            //ensure the model is built
            final Collection<ParserResult> result = new HashSet<ParserResult>(1);
            SourceModel model = SourceModelFactory.getInstance().getModel(fo);
            model.runUserActionTask(new CancellableTask<CompilationInfo>() {

                public void cancel() {
                }

                public void run(CompilationInfo ci) throws Exception {
                    ParserResult presult = ci.getEmbeddedResults("text/x-css").iterator().next();
                    result.add(presult);
                }
            }, true);

            ParserResult presult = result.iterator().next();
            SimpleNode root = ((CSSParserResult) presult).root();
            if (containsErrors(presult)) {
                support.firePropertyChange(MODEL_INVALID, rules, null);
                return;
            }

            updateModel(root);


        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }

    }

    private boolean containsErrors(ParserResult result) {
        //check for errors
        for (Error error : result.getDiagnostics()) {
            if (error.getSeverity() == Severity.ERROR) {
                return true;
            }
        }
        return false;
    }

    private CssModel(SimpleNode root) {
        //the domain model object may mutate during their livecycle 
        //based on user modifications since the underlying source of this model
        //is immutable
        this.immutable = false;
        updateModel(root);
    }

    /** @return List of {@link CssRule}s or null if the document hasn't been parsed yet. */
    public List<CssRule> rules() {
        synchronized (rules) {
            return rules;
        }
    }

    /** Finds a rule on the given offset.
     * 
     * @return Instance of {@link CssRule} for the given offset or null if the 
     * offset falls into a space where there is no css rule.
     * @param offset within the model's document
     */
    public CssRule ruleForOffset(int offset) {
        synchronized (rules) {
            if (rules != null) {
                for (CssRule rule : rules()) {
                    if (rule.getRuleNameOffset() <= offset && rule.getRuleCloseBracketOffset() >= offset) {
                        return rule;
                    }
                }
            }
            return null;
        }
    }

    /** Adds a PropertyChangeListener instance.
     * The listener then receives {@link MODEL_UPDATED}, 
     * {@link MODEL_PARSING} and {@link MODEL_INVALID}.
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    /** Removes a PropertyChangeListener instance. */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    private boolean isImmutable() {
        return immutable;
    }

    private synchronized void updateModel(SimpleNode root) {
        synchronized (rules) {
            List<CssRule> oldRules = rules;

            final List<CssRule> newRules = new ArrayList<CssRule>(oldRules.size());

            NodeVisitor styleRuleVisitor = new NodeVisitor() {

                public void visit(SimpleNode node) {
                    try {
                        if (node.kind() == CSSParserTreeConstants.JJTSTYLERULE) {
                            //find curly brackets
                            Token t = node.jjtGetFirstToken();
                            Token last = node.jjtGetLastToken();

                            int openCurlyBracketOffset = -1;
                            int closeCurlyBracketOffset = -1;
                            ArrayList<Integer> semicolons = new ArrayList<Integer>();
                            ArrayList<Integer> colons = new ArrayList<Integer>();
                            while (t != null && t.offset <= last.offset) { //also include the last token
                                if (t.kind == CSSParserConstants.LBRACE) {
                                    openCurlyBracketOffset = t.offset;
                                } else if (t.kind == CSSParserConstants.RBRACE) {
                                    closeCurlyBracketOffset = t.offset;
                                } else if (t.kind == CSSParserConstants.SEMICOLON) {
                                    semicolons.add(Integer.valueOf(t.offset));
                                } else if (t.kind == CSSParserConstants.COLON) {
                                    colons.add(Integer.valueOf(t.offset));
                                }
                                t = t.next;
                            }

                            //parse style rule
                            SimpleNode selectortList = SimpleNodeUtil.getChildByType(node, CSSParserTreeConstants.JJTSELECTORLIST);
                            SimpleNode[] declarations = SimpleNodeUtil.getChildrenByType(node, CSSParserTreeConstants.JJTDECLARATION);
                            List<CssRuleItem> ruleItems = new ArrayList<CssRuleItem>(declarations.length);
                            for (int i = 0; i < declarations.length; i++) {
                                SimpleNode declaration = declarations[i];
                                SimpleNode property = SimpleNodeUtil.getChildByType(declaration, CSSParserTreeConstants.JJTPROPERTY);
                                SimpleNode value = SimpleNodeUtil.getChildByType(declaration, CSSParserTreeConstants.JJTEXPR);

                                int semicolonOffset = i < semicolons.size() ? semicolons.get(i) : -1; //there may not be the semicolon after last declaration
                                int colonOffset = colons.get(i);

                                CssRuleItem ruleItem = new CssRuleItem(doc, property.image(), property.startOffset(), value.image(), value.startOffset(), colonOffset, semicolonOffset);

                                ruleItems.add(ruleItem);
                            }

                            CssRuleContent styleData = new CssRuleContent(ruleItems, isImmutable());
                            String ruleName = selectortList.image().trim();
                            CssRule rule = new CssRule(doc, ruleName, selectortList.startOffset(),
                                    openCurlyBracketOffset, closeCurlyBracketOffset, styleData);
                            newRules.add(rule);

                        }
                    } catch (BadLocationException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            };

            SimpleNodeUtil.visitChildren(root, styleRuleVisitor);

            if (LOG) {
                LOGGER.fine("CssModel parse tree:");
                root.dump("");
                LOGGER.fine("CssModel structure:");
                for(CssRule rule : newRules) {
                    LOGGER.fine(rule.toString());
                }
            }
            
            rules = newRules;
            support.firePropertyChange(MODEL_UPDATED, oldRules, rules);
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.