blob: ce5d7c4d81052160e63b11d230db32b55f85a5ae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.css.visual;
import org.netbeans.modules.css.visual.spi.RuleHandle;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.css.indexing.api.CssIndex;
import org.netbeans.modules.css.lib.api.CssParserResult;
import org.netbeans.modules.css.model.api.Model;
import org.netbeans.modules.css.model.api.ModelVisitor;
import org.netbeans.modules.css.model.api.Rule;
import org.netbeans.modules.css.model.api.StyleSheet;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.web.common.api.DependenciesGraph;
import org.netbeans.modules.web.common.api.WebUtils;
import org.openide.filesystems.FileObject;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
/**
* Model for stylesheets related to a file.
*
* TODO: I believe I cannot "get" a cached instance of CssIndex as if for
* example classpath of the project changes, we need to get a new one.
*
* @author marekfukala
*/
public class DocumentViewModel implements ChangeListener {
private final FileObject file;
private Project project;
private CssIndex index;
private boolean needsRefresh;
private final ChangeSupport changeSupport;
private boolean initialized;
/**
* Map of stylesheet -> list of rules
*/
private Map<FileObject, List<RuleHandle>> relatedStylesheets;
//created in EDT, no IO here
public DocumentViewModel(FileObject file) {
this.file = file;
changeSupport = new ChangeSupport(this);
needsRefresh = true;
}
FileObject getFile() {
return file;
}
private synchronized void initialize() {
if (!initialized) {
project = FileOwnerQuery.getOwner(file);
if (project == null) {
//no project, no related stylesheets
relatedStylesheets = Collections.emptyMap();
needsRefresh = false;
return;
}
try {
index = CssIndex.get(project);
index.addChangeListener(this);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
initialized = true;
}
}
public void addChangeListener(ChangeListener l) {
changeSupport.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
changeSupport.removeChangeListener(l);
}
void dispose() {
if (index != null) {
index.removeChangeListener(this);
}
}
/*
* ChangeEvents from CssIndex.
*/
@Override
public void stateChanged(ChangeEvent ce) {
//the project has been reindexed, update the map.
needsRefresh = true;
changeSupport.fireChange();
}
/**
* Gets a map of stylesheets -> list of rules.
*
* <b>The method must be called in parsing thread!</b>
*/
public Map<FileObject, List<RuleHandle>> getFilesToRulesMap() {
assert !EventQueue.isDispatchThread();
// assert ParserManager.isParsingThread(); //TODO uncomment once resolved: https://netbeans.org/bugzilla/show_bug.cgi?id=228251
initialize();
if (needsRefresh) {
update();
needsRefresh = false;
}
return relatedStylesheets;
}
private void update() {
relatedStylesheets = new HashMap<>();
DependenciesGraph dependencies = index.getDependencies(file);
Collection<FileObject> allRelatedFiles = dependencies.getAllReferedFiles();
for (final FileObject related : allRelatedFiles) {
if (isStyleSheet(related)) {
Source source = Source.create(related);
try {
ParserManager.parse(Collections.singleton(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
ResultIterator ri = WebUtils.getResultIterator(resultIterator, "text/css"); //NOI18N
if (ri != null) {
final CssParserResult result = (CssParserResult) ri.getParserResult();
if (result != null) {
final Model model = Model.getModel(result);
final List<RuleHandle> rules = new ArrayList<>();
final ModelVisitor visitor = new ModelVisitor.Adapter() {
@Override
public void visitRule(Rule rule) {
rules.add(RuleHandle.createRuleHandle(rule));
}
};
model.runReadTask(new Model.ModelTask() {
@Override
public void run(StyleSheet styleSheet) {
styleSheet.accept(visitor);
}
});
relatedStylesheets.put(related, rules);
}
}
}
});
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}
private boolean isStyleSheet(FileObject file) {
return "text/css".equals(file.getMIMEType()); //NOI18N
}
}