/*
* #%L
* ACS AEM Tools Bundle
* %%
* Copyright (C) 2016 Adobe
* %%
* 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.
* #L%
*/
package com.adobe.acs.tools.clientlib_optimizer.impl;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import com.adobe.granite.ui.clientlibs.ClientLibrary;
import com.adobe.granite.ui.clientlibs.LibraryType;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClientLibraryDependency {
private final ClientLibrary library;
private final ClientLibraryDependency parent;
private final LibraryType type;
private final boolean isEmbed;
private final SortedSet<String> requestedCategories;
private static final Logger log = LoggerFactory.getLogger(ClientLibraryDependency.class);
public ClientLibraryDependency(ClientLibraryDependency parent, ClientLibrary library, Set<String> requestedCategories, boolean isEmbed, LibraryType type) {
this.library = library;
this.parent = parent;
if (isDependencyLoop(parent, library.getPath())) {
throw new DependencyLoopException("Dependency loop detected: " + getStackTrace(parent, library.getPath()));
}
this.type = type;
this.isEmbed = isEmbed;
this.requestedCategories = new TreeSet<String>(java.util.Collections.reverseOrder());
// only the requested categories are relevant (from all the categories of this library)
for (String category : library.getCategories()) {
if (requestedCategories.contains(category)) {
this.requestedCategories.add(category);
}
}
}
/**
* Build the dependency tree of this client library and return the necessary categories in the correct order!
* @param categories
* @return
*/
public List<String> buildDependencyTree(List<String> categories, int currentPosition) {
log.debug("Giving out dependencies for {}", getStackTrace(parent, library.getPath()));
// only consider entry if it is of the required type!
if (library.getTypes().contains(type)) {
for (String category : requestedCategories) {
if (categories.contains(category)) {
log.debug("Category {} already in list, not adding twice!", category, library.getPath());
if (currentPosition < categories.indexOf(category)) {
// if duplicate is after the current position move it directly in front of current position
categories.remove(category);
categories.add(currentPosition, category);
log.debug("Move category {} to the current position because it is needed earlier!", category);
} else {
log.debug("No need to move category because it is loaded early enough!");
// move current position to make sure the dependent libraries are also loaded early enough
currentPosition = categories.indexOf(category);
}
} else {
categories.add(currentPosition, category);
}
}
} else {
log.debug("Not considering categories of this client library because they have the wrong type {}, request was type {}", library.getTypes(), type);
}
// add embedded libraries (per type)
// always embed all types to correctly include transitive categories of the right type (even if intermediate embed has the wrong type)
log.debug("Processing embedded JS libraries of library with path {}", library.getPath());
addLibraries(categories, true, library.getEmbedded(LibraryType.JS), library.getEmbeddedCategories(), currentPosition);
log.debug("Processing embedded CSS libraries of library with path {}", library.getPath());
addLibraries(categories, true, library.getEmbedded(LibraryType.CSS), library.getEmbeddedCategories(), currentPosition);
log.debug("Processing dependent libraries of library with path {}", library.getPath());
// add dependent libraries
// current position might move
addLibraries(categories, false, library.getDependencies(false), library.getDependentCategories(), currentPosition);
return categories;
}
private List<String> addLibraries(List<String> categories, boolean isEmbed, Map<String, ? extends ClientLibrary> librariesMap, String[] requestedCategories, int currentPosition) {
// the order is given by the paths
// we just sort alphabetically in here
TreeMap<String, ClientLibrary> sortedLibrariesMap = new TreeMap<String, ClientLibrary>(librariesMap);
// add in reverse order (because each might add a number of dependent libraries)
for (Map.Entry<String, ClientLibrary> entry: sortedLibrariesMap.descendingMap().entrySet()) {
ClientLibraryDependency dependency = new ClientLibraryDependency(this, entry.getValue(), new HashSet<String>(Arrays.asList(requestedCategories)), isEmbed, type);
categories = dependency.buildDependencyTree(categories, currentPosition);
}
return categories;
}
public static boolean isDependencyLoop(ClientLibraryDependency parent, String path) {
while (parent != null) {
if (path.equals(parent.library.getPath())) {
return true;
}
parent = parent.parent;
}
return false;
}
public static String getStackTrace(ClientLibraryDependency parent, String currentPath) {
// give out all paths from the root
StringBuffer tmp = new StringBuffer();
printStack(tmp, parent);
tmp.append(currentPath);
return tmp.toString();
}
private static void printStack(StringBuffer buffer, ClientLibraryDependency parent) {
if (parent != null) {
printStack(buffer, parent.parent);
buffer.append(parent.toString() + "->");
}
}
public String toString() {
return library.getPath() + "[cat:" + StringUtils.join(library.getCategories()) + ", embed:"+ isEmbed +", type: "+ library.getTypes() +"]";
}
}