/*
* Copyright (c) 2006-2013 by Public Library of Science http://plos.org http://ambraproject.org
* 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.0Unless 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.ambraproject.util;
import org.ambraproject.views.CategoryView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* Utilities for working with Maps.
* <p/>
* TODO: consider mergine this code with org.ambraproject.service.taxonomy.TaxonomyServiceImpl.
* In practice, TaxonomyService code mostly delegates to hibernate and solr, while this class
* deals with taxonomy data structures. But the distinction isn't clear-cut.
*/
public class CategoryUtils {
private static final Logger log = LoggerFactory.getLogger(CategoryUtils.class);
private CategoryUtils() {}
/**
* For the top elements: return keys and the count of children
*
* @param categoryView
*
* @return a map of keys and the count of children
*/
@SuppressWarnings("unchecked")
public static Map<String, Integer> keyCounts(CategoryView categoryView) {
Map<String, Integer> results = new HashMap<String, Integer>();
for(String key : categoryView.getChildren().keySet()) {
//The size call below as this is a ConcurrentSkipListMap, might be expensive
results.put(key, categoryView.getChild(key).getChildren().size());
}
return results;
}
/**
* For the top elements: return keys and the immediate children
*
* @param categoryView
*
* @return a map of keys and the immediate children
*/
@SuppressWarnings("unchecked")
public static Map<String, SortedSet<String>> getShortTree(CategoryView categoryView) {
//Use sorted map
Map<String, SortedSet<String>> results = new ConcurrentSkipListMap<String, SortedSet<String>>();
for(String key : categoryView.getChildren().keySet()) {
ConcurrentSkipListSet sortedSet = new ConcurrentSkipListSet();
sortedSet.addAll(categoryView.getChild(key).getChildren().keySet());
results.put(key, sortedSet);
}
return results;
}
/**
* Return a map that contains entries that match the filters given
*
* Any match in a tree will result in that branch being included
*
* @param categoryView the categoryView to filter
* @param filters the filters to apply
*
* @return a new map
*/
@SuppressWarnings("unchecked")
public static CategoryView filterMap(CategoryView categoryView, String[] filters) {
CategoryView finalRes = new CategoryView(categoryView.getName());
for(String key : categoryView.getChildren().keySet()) {
CategoryView res = filterMap(categoryView.getChild(key), filters);
if(!res.getChildren().isEmpty()) {
finalRes.addChild(res);
}
for(String filter : filters) {
if(key.toLowerCase().contains(filter.toLowerCase())) {
finalRes.addChild(filterMap(categoryView.getChild(key), filters));
}
}
}
return finalRes;
}
/**
* For the passed in category, find the matching CategoryView. This will come in handy when looking for
* getting the correctly formatted case corrected category name
*
* @param categoryView the categoryView to search from
* @param category the string of the category to search for
*
* @return The first matching category view
*
* @throws org.ambraproject.ApplicationException
*/
public static CategoryView findCategory(CategoryView categoryView, String category) {
if(categoryView.getName().toLowerCase().equals(category.toLowerCase())) {
return categoryView;
}
for(String key : categoryView.getChildren().keySet()) {
CategoryView res = findCategory(categoryView.getChild(key), category);
if(res != null) {
return res;
}
}
return null;
}
/**
* Given a list of "/" delimited strings build a structured map
*
* @param categories list of Pairs wrapping the category name and article count
*
* @return a new treeMap
*/
public static CategoryView createMapFromStringList(List<String> categories) {
CategoryView root = new CategoryView("ROOT");
// Since there can be multiple paths to the same child, we don't want to create the same
// child more than once. Hence the need for this map.
Map<String, CategoryView> createdCategories = new HashMap<String, CategoryView>();
for (String category : categories) {
if(category.charAt(0) == '/') {
//Ignore first "/"
root = recurseValues(root, category.substring(1).split("\\/"), 0, createdCategories);
} else {
root = recurseValues(root, category.split("\\/"), 0, createdCategories);
}
}
// See comment below.
// incrementAncestorCounts(root);
return root;
}
private static CategoryView recurseValues(CategoryView root, String categories[], int index,
Map<String, CategoryView> created) {
CategoryView child = root.getChildren().get(categories[index]);
if (child == null) {
child = created.get(categories[index]);
if (child == null) {
child = new CategoryView(categories[index]);
created.put(categories[index], child);
}
root.addChild(child);
}
if ((index + 1) < categories.length) { // path end
recurseValues(child, categories, index + 1, created);
}
return root;
}
}