/** * Copyright 2010 Molindo GmbH * * Licensed 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.wicketstuff.mergedresources.annotations; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.util.string.Strings; import org.wicketstuff.config.MatchingResources; import org.wicketstuff.mergedresources.ResourceSpec; import edu.umd.cs.findbugs.annotations.SuppressWarnings; /** * Gather page resources to merge, depends on {@link CssContribution} and * {@link JsContribution} annotations. * * Helper to make using wicketstuff-merged-resources easier. */ public class ContributionScanner { private static final String DEFAULT_PATH_JS = "all.js"; private static final String DEFAULT_PATH_CSS = "all.css"; private final Map<String, SortedSet<WeightedResourceSpec>> _contributions; public ContributionScanner(String packageName) { MatchingResources resources = new MatchingResources(getPatternForPackage(packageName)); _contributions = scan(resources); } private Map<String, SortedSet<WeightedResourceSpec>> scan(MatchingResources resources) { Map<String, SortedSet<WeightedResourceSpec>> contributions = new HashMap<String, SortedSet<WeightedResourceSpec>>(); for (Class<?> cls : resources.getAnnotatedMatches(JsContribution.class)) { JsContribution a = cls.getAnnotation(JsContribution.class); addJsContributions(cls, a, contributions); } for (Class<?> cls : resources.getAnnotatedMatches(CssContribution.class)) { CssContribution a = cls.getAnnotation(CssContribution.class); addCssContributions(cls, a, contributions); } for (Class<?> cls : resources.getAnnotatedMatches(CssContributions.class)) { CssContributions cssMulti = cls.getAnnotation(CssContributions.class); for (CssContribution css : cssMulti.value()) { addCssContributions(cls, css, contributions); } } for (Class<?> cls : resources.getAnnotatedMatches(ResourceContribution.class)) { ResourceContribution resource = cls.getAnnotation(ResourceContribution.class); addResourceContributions(cls, resource, contributions); } for (Map.Entry<String, SortedSet<WeightedResourceSpec>> e : contributions.entrySet()) { e.setValue(Collections.unmodifiableSortedSet(e.getValue())); } return Collections.unmodifiableMap(contributions); } private void addJsContributions(Class<?> scope, JsContribution js, Map<String, SortedSet<WeightedResourceSpec>> contributions) { for (String file : js.value()) { if (Strings.isEmpty(file)) { file = scope.getSimpleName() + ".js"; } String path = Strings.isEmpty(js.path()) ? DEFAULT_PATH_JS : js.path(); SortedSet<WeightedResourceSpec> specs = contributions.get(path); if (specs == null) { specs = new TreeSet<WeightedResourceSpec>(WeightedResourceSpecComparator.INSTANCE); contributions.put(path, specs); } if (!specs.add(new WeightedResourceSpec(scope, file, js.order()))) { throw new WicketRuntimeException("duplicate resource contribution: " + js + ", scope=" + scope); } } } private void addCssContributions(Class<?> scope, CssContribution css, Map<String, SortedSet<WeightedResourceSpec>> contributions) { for (String file : css.value()) { if (Strings.isEmpty(file)) { file = getDefaultCssFile(scope.getSimpleName(), css.media()); } String path = Strings.isEmpty(css.path()) ? getDefaultCssPath(css.media()) : css.path(); SortedSet<WeightedResourceSpec> specs = contributions.get(path); if (specs == null) { specs = new TreeSet<WeightedResourceSpec>(WeightedResourceSpecComparator.INSTANCE); contributions.put(path, specs); } if (!specs.add(new WeightedResourceSpec(scope, file, css.order()))) { throw new WicketRuntimeException("duplicate resource contribution: " + css + ", scope=" + scope); } } } static String getDefaultCssFile(String simpleName, String media) { if (!Strings.isEmpty(media) && !"all".equals(media)) { return simpleName + "-" + media + ".css"; } return simpleName + ".css"; } static String getDefaultCssPath(String media) { if (!Strings.isEmpty(media)) { return media + ".css"; } return DEFAULT_PATH_CSS; } private void addResourceContributions(Class<?> scope, ResourceContribution resource, Map<String, SortedSet<WeightedResourceSpec>> contributions) { for (String file : resource.value()) { if (Strings.isEmpty(file)) { throw new WicketRuntimeException("empty file name not allowed for @ResourceContributions at class " + scope.getName()); } // don't merge resources by default String path = Strings.isEmpty(resource.path()) ? file : resource.path(); SortedSet<WeightedResourceSpec> specs = contributions.get(path); if (specs == null) { specs = new TreeSet<WeightedResourceSpec>(WeightedResourceSpecComparator.INSTANCE); contributions.put(path, specs); } if (!specs.add(new WeightedResourceSpec(scope, file))) { throw new WicketRuntimeException("duplicate resource contribution: " + resource + ", scope=" + scope); } } } /** * @return an unmodifiable map of contributions mapped by scope */ public Map<String, SortedSet<WeightedResourceSpec>> getContributions() { return _contributions; } /** * Get the Spring search pattern given a package name or part of a package * name * * @param packageName * a package name * @return a Spring search pattern for the given package */ private String getPatternForPackage(String packageName) { if (packageName == null) { packageName = ""; } packageName = packageName.replace('.', '/'); if (!packageName.endsWith("/")) { packageName += '/'; } return "classpath*:" + packageName + "**/*.class"; } @SuppressWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "super type is sufficient, ignore weight") public static final class WeightedResourceSpec extends ResourceSpec { private static final long serialVersionUID = 1L; private final int _weight; public WeightedResourceSpec(Class<?> scope, String file, int weight) { super(scope, file); _weight = weight; } public WeightedResourceSpec(Class<?> scope, String file) { this(scope, file, 0); } @Override public String toString() { return super.toString() + " (weight=" + _weight + ")"; } } public enum WeightedResourceSpecComparator implements Comparator<WeightedResourceSpec> { INSTANCE; @Override public int compare(WeightedResourceSpec o1, WeightedResourceSpec o2) { if (o1 == null) { return o2 == null ? 0 : -1; } else if (o2 == null) { return 1; } if (o1.equals(o2)) { return 0; } // from highest to lowest - avoid overflow int val = Integer.valueOf(o2._weight).compareTo(o1._weight); if (val != 0) { return val; } val = o1.getFile().compareTo(o2.getFile()); if (val != 0) { return val; } return o1.getScope().getName().compareTo(o2.getScope().getName()); } } }