| /** |
| * 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); |
| } |
| } |
| |
| } |
| |
| } |