blob: 46af0902132395006346fefb1c58af73aff8b760 [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.web.common.api;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.project.Project;
import org.netbeans.modules.web.common.cssprep.CssPreprocessorAccessor;
import org.netbeans.modules.web.common.cssprep.CssPreprocessorsAccessor;
import org.netbeans.modules.web.common.spi.CssPreprocessorImplementation;
import org.netbeans.modules.web.common.spi.CssPreprocessorImplementationListener;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Parameters;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.Lookups;
/**
* This class provides access to the list of registered CSS preprocessors. The path
* for registration is "{@value #PREPROCESSORS_PATH}" on SFS.
* <p>
* This class is thread safe.
* @since 1.37
*/
public final class CssPreprocessors {
/**
* Path on SFS for CSS preprocessors registrations.
*/
public static final String PREPROCESSORS_PATH = "CSS/PreProcessors"; // NOI18N
private static final RequestProcessor RP = new RequestProcessor(CssPreprocessors.class.getName(), 2);
private static final Lookup.Result<CssPreprocessorImplementation> PREPROCESSORS = Lookups.forPath(PREPROCESSORS_PATH).lookupResult(CssPreprocessorImplementation.class);
private static final CssPreprocessors INSTANCE = new CssPreprocessors();
private final List<CssPreprocessor> preprocessors = new CopyOnWriteArrayList<>();
final CssPreprocessorsListener.Support listenersSupport = new CssPreprocessorsListener.Support();
private final PreprocessorImplementationsListener preprocessorImplementationsListener = new PreprocessorImplementationsListener();
static {
PREPROCESSORS.addLookupListener(new LookupListener() {
@Override
public void resultChanged(LookupEvent ev) {
INSTANCE.reinitProcessors();
}
});
CssPreprocessorsAccessor.setDefault(new CssPreprocessorsAccessor() {
@Override
public List<CssPreprocessor> getPreprocessors() {
return INSTANCE.getPreprocessors();
}
});
}
private CssPreprocessors() {
initProcessors();
}
/**
* Get CssPreprocessors instance.
* @return CssPreprocessors instance
* @since 1.40
*/
public static CssPreprocessors getDefault() {
return INSTANCE;
}
/**
* Get CSS preprocessor for the given identifier.
* @param cssPreprocessorIdentifier identifier
* @return CSS preprocessor for the given identifier or {@code null} if no CSS preprocessor is found
* @since 1.87
*/
@CheckForNull
public CssPreprocessor getCssPreprocessor(String cssPreprocessorIdentifier) {
Parameters.notNull("cssPreprocessorIdentifier", cssPreprocessorIdentifier); // NOI18N
for (CssPreprocessor cssPreprocessor : preprocessors) {
if (cssPreprocessorIdentifier.equals(cssPreprocessor.getIdentifier())) {
return cssPreprocessor;
}
}
return null;
}
List<CssPreprocessor> getPreprocessors() {
return new ArrayList<>(preprocessors);
}
/**
* Attach a listener that is to be notified of changes
* in CSS preprocessors.
* @param listener a listener, can be {@code null}
* @since 1.44
*/
public void addCssPreprocessorsListener(@NullAllowed CssPreprocessorsListener listener) {
listenersSupport.addCssPreprocessorListener(listener);
}
/**
* Removes a change listener.
* @param listener a listener, can be {@code null}
* @since 1.44
*/
public void removeCssPreprocessorsListener(@NullAllowed CssPreprocessorsListener listener) {
listenersSupport.removeCssPreprocessorListener(listener);
}
/**
* Process given file (can be a folder as well) by {@link #getPreprocessors() all CSS preprocessors}.
* <b>The project must have {@link org.netbeans.modules.web.common.spi.ProjectWebRootProvider}
* in its lookup.</b>
* <p>
* For detailed information see {@link CssPreprocessorImplementation#process(Project, FileObject, String, String)}.
* @param project project where the file belongs, can be {@code null} for file without a project
* @param fileObject valid or even invalid file (or folder) to be processed
* @see #process(Project, FileObject, String, String)
* @since 1.42
*/
public void process(@NullAllowed final Project project, @NonNull final FileObject fileObject) {
Parameters.notNull("fileObject", fileObject); // NOI18N
processInternal(getPreprocessors(), project, fileObject, null, null);
}
/**
* Same as {@link #process(Project, FileObject)} but for rename operation so original name and extension
* is provided as well.
* @param project project where the file belongs, can be {@code null} for file without a project
* @param fileObject valid file (or folder) to be processed
* @param originalName original file name
* @param originalExtension original file extension
* @see #process(Project, FileObject)
* @since 1.52
*/
public void process(@NullAllowed final Project project, @NonNull final FileObject fileObject,
@NonNull String originalName, @NonNull String originalExtension) {
Parameters.notNull("fileObject", fileObject); // NOI18N
Parameters.notNull("originalName", originalName); // NOI18N
Parameters.notNull("originalExtension", originalExtension); // NOI18N
processInternal(getPreprocessors(), project, fileObject, originalName, originalExtension);
}
/**
* Process given file (can be a folder as well) by the given CSS preprocessor.
* <p>
* For detailed information see {@link CssPreprocessorImplementation#process(Project, FileObject, String, String)}.
* @param cssPreprocessor CSS preprocesor
* @param project project where the file belongs, can be {@code null} for file without a project
* @param fileObject valid or even invalid file (or folder) to be processed
* @see #process(CssPreprocessor, Project, FileObject, String, String)
* @see #getCssPreprocessor(String)
* @since 1.42
*/
public void process(@NonNull CssPreprocessor cssPreprocessor, @NullAllowed final Project project, @NonNull final FileObject fileObject) {
Parameters.notNull("cssPreprocessor", cssPreprocessor); // NOI18N
Parameters.notNull("fileObject", fileObject); // NOI18N
processInternal(Collections.singletonList(cssPreprocessor), project, fileObject, null, null);
}
/**
* Same as {@link #process(CssPreprocessor, Project, FileObject)} but for rename operation so original name and extension
* is provided as well.
* @param cssPreprocessor CSS preprocesor
* @param project project where the file belongs, can be {@code null} for file without a project
* @param fileObject valid file (or folder) to be processed
* @param originalName original file name
* @param originalExtension original file extension
* @see #process(CssPreprocessor, Project, FileObject)
* @see #getCssPreprocessor(String)
* @since 1.52
*/
public void process(@NonNull CssPreprocessor cssPreprocessor, @NullAllowed final Project project, @NonNull final FileObject fileObject,
@NonNull String originalName, @NonNull String originalExtension) {
Parameters.notNull("cssPreprocessor", cssPreprocessor); // NOI18N
Parameters.notNull("fileObject", fileObject); // NOI18N
Parameters.notNull("originalName", originalName); // NOI18N
Parameters.notNull("originalExtension", originalExtension); // NOI18N
processInternal(Collections.singletonList(cssPreprocessor), project, fileObject, originalName, originalExtension);
}
void processInternal(final List<CssPreprocessor> preprocessors, final Project project, final FileObject fileObject,
final String originalName, final String originalExtension) {
RP.post(new Runnable() {
@Override
public void run() {
for (CssPreprocessor cssPreprocessor : preprocessors) {
cssPreprocessor.getDelegate().process(project, fileObject, originalName, originalExtension);
}
}
});
}
private void initProcessors() {
assert preprocessors.isEmpty() : "Empty preprocessors expected but: " + preprocessors;
preprocessors.addAll(map(PREPROCESSORS.allInstances()));
for (CssPreprocessor cssPreprocessor : preprocessors) {
cssPreprocessor.getDelegate().addCssPreprocessorListener(preprocessorImplementationsListener);
}
}
void reinitProcessors() {
synchronized (preprocessors) {
clearProcessors();
initProcessors();
}
listenersSupport.firePreprocessorsChanged();
}
private void clearProcessors() {
for (CssPreprocessor cssPreprocessor : preprocessors) {
cssPreprocessor.getDelegate().removeCssPreprocessorListener(preprocessorImplementationsListener);
}
preprocessors.clear();
}
@CheckForNull
CssPreprocessor findCssPreprocessor(CssPreprocessorImplementation cssPreprocessorImplementation) {
assert cssPreprocessorImplementation != null;
for (CssPreprocessor cssPreprocessor : preprocessors) {
if (cssPreprocessor.getDelegate() == cssPreprocessorImplementation) {
return cssPreprocessor;
}
}
assert false : "Cannot find CSS preprocessor for implementation: " + cssPreprocessorImplementation.getIdentifier();
return null;
}
//~ Mappers
private List<CssPreprocessor> map(Collection<? extends CssPreprocessorImplementation> preprocessors) {
List<CssPreprocessor> result = new ArrayList<>();
for (CssPreprocessorImplementation cssPreprocessor : preprocessors) {
result.add(CssPreprocessorAccessor.getDefault().create(cssPreprocessor));
}
return result;
}
//~ Inner classes
private final class PreprocessorImplementationsListener implements CssPreprocessorImplementationListener {
@Override
public void optionsChanged(CssPreprocessorImplementation cssPreprocessor) {
Parameters.notNull("cssPreprocessor", cssPreprocessor); // NOI18N
CssPreprocessor preprocessor = findCssPreprocessor(cssPreprocessor);
if (preprocessor != null) {
listenersSupport.fireOptionsChanged(preprocessor);
}
}
@Override
public void customizerChanged(Project project, CssPreprocessorImplementation cssPreprocessor) {
Parameters.notNull("project", project); // NOI18N
Parameters.notNull("cssPreprocessor", cssPreprocessor); // NOI18N
CssPreprocessor preprocessor = findCssPreprocessor(cssPreprocessor);
if (preprocessor != null) {
listenersSupport.fireCustomizerChanged(project, preprocessor);
}
}
@Override
public void processingErrorOccured(Project project, CssPreprocessorImplementation cssPreprocessor, String error) {
Parameters.notNull("project", project); // NOI18N
Parameters.notNull("cssPreprocessor", cssPreprocessor); // NOI18N
Parameters.notNull("error", error); // NOI18N
CssPreprocessor preprocessor = findCssPreprocessor(cssPreprocessor);
if (preprocessor != null) {
listenersSupport.fireProcessingErrorOccured(project, preprocessor, error);
}
}
}
}