/** * Copyright (c) 2000-2017 Liferay, Inc. All rights reserved. * * 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 com.liferay.faces.util.config.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import com.liferay.faces.util.logging.Logger; import com.liferay.faces.util.logging.LoggerFactory; /** * @author Vernon Singleton */ public class OrderingUtil { // Logger private static final Logger logger = LoggerFactory.getLogger(OrderingUtil.class); // Private Constants private static final int MAX_ATTEMPTS = (Integer.MAX_VALUE / (Byte.MAX_VALUE * Byte.MAX_VALUE * Byte.MAX_VALUE)); // 1048 public static Map<String, FacesConfigDescriptor> getConfigMap(List<FacesConfigDescriptor> facesConfigDescriptors) { Map<String, FacesConfigDescriptor> configMap = new HashMap<String, FacesConfigDescriptor>(); for (FacesConfigDescriptor facesConfigDescriptor : facesConfigDescriptors) { String name = facesConfigDescriptor.getName(); configMap.put(name, facesConfigDescriptor); } return configMap; } /** * This method returns an ordered version of the specified list of faces-config.xml descriptors and assumes that * there is no absolute ordering. */ public static List<FacesConfigDescriptor> getOrder(List<FacesConfigDescriptor> configList) throws OrderingBeforeAndAfterException, OrderingCircularDependencyException, OrderingMaxAttemptsException { if (logger.isTraceEnabled()) { for (FacesConfigDescriptor config : configList) { String name = config.getName(); Ordering ordering = config.getOrdering(); if (ordering != null) { EnumMap<Ordering.Path, String[]> routes = ordering.getRoutes(); if (routes != null) { String[] beforeRoutes = routes.get(Ordering.Path.BEFORE); if (beforeRoutes.length != 0) { logger.trace("before name=[{0}] b routes=[{1}] beforeOthers=[{2}]", name, Arrays.asList(beforeRoutes).toString(), ordering.isBeforeOthers()); } String[] afterRoutes = routes.get(Ordering.Path.AFTER); if (afterRoutes.length != 0) { logger.trace("before name=[{0}] a routes=[{1}] afterOthers=[{2}]", name, Arrays.asList(afterRoutes).toString(), ordering.isAfterOthers()); } } } } } // Check for "duplicate name exception" and "circular references" as described in 11.4.8 Ordering of Artifacts checkForSpecExceptions(configList); if (logger.isTraceEnabled()) { for (FacesConfigDescriptor config : configList) { String name = config.getName(); Ordering ordering = config.getOrdering(); if (ordering != null) { EnumMap<Ordering.Path, String[]> routes = ordering.getRoutes(); if (routes != null) { String[] beforeRoutes = routes.get(Ordering.Path.BEFORE); if (beforeRoutes.length != 0) { logger.trace("after name=[{0}] b routes=[{1}] beforeOthers=[{2}]", name, Arrays.asList(beforeRoutes).toString(), ordering.isBeforeOthers()); } String[] afterRoutes = routes.get(Ordering.Path.AFTER); if (afterRoutes.length != 0) { logger.trace("after name=[{0}] b routes=[{1}] afterOthers=[{2}]", name, Arrays.asList(afterRoutes).toString(), ordering.isAfterOthers()); } } } } } // Sort the documents such that specified ordering will be considered. // // It turns out that some of the specified ordering, if it was not discovered by the sort routine // until later in its processing, was not being considered correctly in the ordering algorithm. // // This preSort method puts all of the documents with specified ordering as early on in the // list of documents as possible for to consider it quickly, and be // able to use its ordering algorithm to the best of its ability to achieve the specified ordering. configList = preSort(configList); FacesConfigDescriptor[] configs = configList.toArray(new FacesConfigDescriptor[configList.size()]); // This is a multiple pass sorting routine which gets the documents close to the order they need to be in innerSort(configs); // This is the final sort which checks the list from left to right to see if they are in the specified order and // if they are not, it moves the incorrectly placed document(s) to the right into its proper place, and shifts // others left as necessary. postSort(configs); return new ArrayList<FacesConfigDescriptor>(Arrays.asList(configs)); } /** * This method returns an ordered version of the specified list of faces-config.xml descriptors, taking the * specified absolute ordering into account. */ public static List<FacesConfigDescriptor> getOrder(List<FacesConfigDescriptor> configs, List<String> absoluteOrder) { List<FacesConfigDescriptor> orderedList = new ArrayList<FacesConfigDescriptor>(); List<FacesConfigDescriptor> configList = new CopyOnWriteArrayList<FacesConfigDescriptor>(); configList.addAll(configs); for (String name : absoluteOrder) { if (Ordering.OTHERS.equals(name)) { continue; } boolean found = false; for (FacesConfigDescriptor config : configList) { if (!found && name.equals(config.getName())) { found = true; orderedList.add(config); configList.remove(config); } else if (found && name.equals(config.getName())) { logger.warn("name=[{0}] found more than once", name); break; } } if (!found) { logger.warn("name=[{0}] specified in absolute-ordering was not found", name); } } int othersIndex = absoluteOrder.indexOf(Ordering.OTHERS); if (othersIndex != -1) { for (FacesConfigDescriptor config : configList) { orderedList.add(othersIndex, config); } } return orderedList; } private static String[] appendAndSort(String[]... groups) { HashMap<String, Integer> map = new HashMap<String, Integer>(); // retain OTHERS, if it is in the first group, but do not allow OTHERS to be appended if (groups[0] != null) { if (containsOthers(groups[0])) { map.put(Ordering.OTHERS, 1); } } for (String[] group : groups) { for (String name : group) { if (!name.equals(Ordering.OTHERS)) { map.put(name, 1); } } } Set<String> keySet = map.keySet(); String[] orderedNames = keySet.toArray(new String[keySet.size()]); Arrays.sort(orderedNames); return orderedNames; } private static void checkForBothBeforeAndAfter(FacesConfigDescriptor config) throws OrderingBeforeAndAfterException { String configName = config.getName(); Ordering configOrdering = config.getOrdering(); EnumMap<Ordering.Path, String[]> orderingRoutes = configOrdering.getRoutes(); HashMap<String, Integer> map = new HashMap<String, Integer>(); String[] beforeRoutes = orderingRoutes.get(Ordering.Path.BEFORE); for (String name : beforeRoutes) { Integer value = map.get(name); if (value == null) { value = 1; } else { value += 1; } map.put(name, value); } String[] afterRoutes = orderingRoutes.get(Ordering.Path.AFTER); for (String name : afterRoutes) { Integer value = map.get(name); if (value == null) { value = 1; } else { value += 1; } map.put(name, value); } Set<String> keySet = map.keySet(); String[] namesToCheck = keySet.toArray(new String[keySet.size()]); for (String name : namesToCheck) { if (map.get(name) > 1) { throw new OrderingBeforeAndAfterException(configName, name); } } } private static void checkForSpecExceptions(List<FacesConfigDescriptor> configs) throws OrderingBeforeAndAfterException, OrderingCircularDependencyException { for (FacesConfigDescriptor config : configs) { // Check for "duplicate name exception" checkForBothBeforeAndAfter(config); // Map the routes along both paths, checking for "circular references" along each path for (Ordering.Path path : Ordering.Path.values()) { mapRoutes(config, path, configs); } } } private static boolean containsOthers(String[] route) { return (Arrays.binarySearch(route, Ordering.OTHERS) >= 0); } private static <K, V extends Comparable<? super V>> Map<K, V> descendingByValue(Map<K, V> map) { List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { public int compare(Map.Entry<K, V> a, Map.Entry<K, V> b) { return (b.getValue()).compareTo(a.getValue()); } }); Map<K, V> result = new LinkedHashMap<K, V>(); for (Map.Entry<K, V> entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } private static LinkedList<String> extractNamesList(FacesConfigDescriptor[] configs) { LinkedList<String> names = new LinkedList<String>(); for (FacesConfigDescriptor config : configs) { names.add(config.getName()); } return names; } private static int innerSort(FacesConfigDescriptor[] configs) throws OrderingMaxAttemptsException { int attempts = 0; boolean attempting = true; while (attempting) { if (attempts > MAX_ATTEMPTS) { throw new OrderingMaxAttemptsException(MAX_ATTEMPTS); } else { attempting = false; } int last = configs.length - 1; for (int i = 0; i < configs.length; i++) { int first = i; int second = first + 1; if (first == last) { second = first; first = 0; } if (isDisordered(configs[first], configs[second])) { FacesConfigDescriptor temp = configs[first]; configs[first] = configs[second]; configs[second] = temp; attempting = true; } } attempts++; } logger.trace("attempt#{0} of {1}", attempts, MAX_ATTEMPTS); return attempts; } private static boolean isDisordered(FacesConfigDescriptor config1, FacesConfigDescriptor config2) { String config1Name = config1.getName(); String config2Name = config2.getName(); Ordering config1Ordering = config1.getOrdering(); Ordering config2Ordering = config2.getOrdering(); if (config1Ordering.isOrdered() && !config2Ordering.isOrdered()) { if ((config1Ordering.getRoutes().get(Ordering.Path.AFTER).length != 0) && !config1Ordering.isBeforeOthers()) { return true; } } // they are not in the specified order if (config2Ordering.isBefore(config1Name) || config1Ordering.isAfter(config2Name)) { return true; } // config1 should be after others, but it is not if (config1Ordering.isAfterOthers() && !config1Ordering.isBefore(config2Name) && !(config1Ordering.isAfterOthers() && config2Ordering.isAfterOthers())) { return true; } // config2 should be before others, but it is not return config2Ordering.isBeforeOthers() && !config2Ordering.isAfter(config1Name) && !(config1Ordering.isBeforeOthers() && config2Ordering.isBeforeOthers()); } private static void mapRoutes(FacesConfigDescriptor config, Ordering.Path path, List<FacesConfigDescriptor> facesConfigs) throws OrderingCircularDependencyException { String configName = config.getName(); Ordering configOrdering = config.getOrdering(); EnumMap<Ordering.Path, String[]> configOrderingRoutes = configOrdering.getRoutes(); String[] routePathNames = configOrderingRoutes.get(path); for (String routePathName : routePathNames) { if (!routePathName.equals(Ordering.OTHERS)) { for (FacesConfigDescriptor otherConfig : facesConfigs) { String otherConfigName = otherConfig.getName(); if (routePathName.equals(otherConfigName)) { Ordering otherConfigOrdering = otherConfig.getOrdering(); EnumMap<Ordering.Path, String[]> otherConfigOrderingRoutes = otherConfigOrdering.getRoutes(); String[] otherRoutePathNames = otherConfigOrderingRoutes.get(path); if (Arrays.binarySearch(otherRoutePathNames, configName) >= 0) { throw new OrderingCircularDependencyException(path, facesConfigs); } // If I am before them, they should be informed that they are after me. Similarly, if I am after // them, then they should be informed that they are before me. Ordering.Path oppositePath; if (path == Ordering.Path.BEFORE) { oppositePath = Ordering.Path.AFTER; } else { oppositePath = Ordering.Path.BEFORE; } if (Arrays.binarySearch(otherConfigOrderingRoutes.get(oppositePath), configName) < 0) { EnumMap<Ordering.Path, String[]> routes = new EnumMap<Ordering.Path, String[]>( Ordering.Path.class); routes.put(path, otherRoutePathNames); routes.put(oppositePath, appendAndSort(otherConfigOrderingRoutes.get(oppositePath), new String[] { configName })); otherConfigOrdering.setRoutes(routes); } // If I am before them and they are before others, then I should be informed that I am before // others too. Similarly, if I am after them and they are after others, then I should be // informed that I am after others too. if (otherRoutePathNames.length > 0) { EnumMap<Ordering.Path, String[]> routes = new EnumMap<Ordering.Path, String[]>( Ordering.Path.class); routes.put(path, appendAndSort(routePathNames, otherRoutePathNames)); routes.put(oppositePath, configOrderingRoutes.get(oppositePath)); configOrdering.setRoutes(routes); } } } } } } private static void postSort(FacesConfigDescriptor[] configs) { int i = 0; while (i < configs.length) { LinkedList<String> names = extractNamesList(configs); boolean done = true; for (int j = 0; j < configs.length; j++) { int k = 0; for (String configName : names) { if (configs[j].getName().equals(configName)) { break; } if (configs[j].getOrdering().isBefore(configName)) { // We have a document that is out of order, and his index is k, he belongs at index j, and all // the documents in between need to be shifted left. FacesConfigDescriptor temp = null; for (int m = 0; m < configs.length; m++) { // This is one that is out of order and needs to be moved. if (m == k) { temp = configs[m]; } // This is one in between that needs to be shifted left. if ((temp != null) && (m != j)) { configs[m] = configs[m + 1]; } // This is where the one that is out of order needs to be moved to. if (m == j) { configs[m] = temp; done = false; break; } } if (!done) { break; } } k = k + 1; } } if (done) { break; } } } private static List<FacesConfigDescriptor> preSort(List<FacesConfigDescriptor> configs) { List<FacesConfigDescriptor> newConfigList = new ArrayList<FacesConfigDescriptor>(); List<FacesConfigDescriptor> anonAndUnordered = new LinkedList<FacesConfigDescriptor>(); Map<String, Integer> namedMap = new LinkedHashMap<String, Integer>(); for (FacesConfigDescriptor config : configs) { Ordering configOrdering = config.getOrdering(); EnumMap<Ordering.Path, String[]> configOrderingRoutes = configOrdering.getRoutes(); String[] beforePathNames = configOrderingRoutes.get(Ordering.Path.BEFORE); String[] afterPathNames = configOrderingRoutes.get(Ordering.Path.AFTER); String configName = config.getName(); if (((configName == null) || (configName.length() == 0)) && !configOrdering.isOrdered()) { anonAndUnordered.add(config); } else { int totalPathNames = beforePathNames.length + afterPathNames.length; namedMap.put(configName, totalPathNames); } } namedMap = descendingByValue(namedMap); Map<String, FacesConfigDescriptor> configMap = getConfigMap(configs); // add named configs to the list in the correct preSorted order for (Map.Entry<String, Integer> entry : namedMap.entrySet()) { String key = entry.getKey(); newConfigList.add(configMap.get(key)); } // add configs that are both anonymous and unordered, to the list in their original, incoming order for (FacesConfigDescriptor config : anonAndUnordered) { newConfigList.add(config); } return newConfigList; } }