blob: 320020841686896f78c199d005aa917cefbd6491 [file] [log] [blame]
/*******************************************************************************
* 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.ofbiz.base.util.collections;
import java.io.Serializable;
import java.util.Locale;
import java.util.Map;
import javax.el.PropertyNotFoundException;
import org.ofbiz.base.lang.IsEmpty;
import org.ofbiz.base.lang.SourceMonitored;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilObject;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.cache.Cache;
import org.ofbiz.base.util.cache.UtilCache;
import org.ofbiz.base.util.string.FlexibleStringExpander;
import org.ofbiz.base.util.string.UelUtil;
/**
* Used to flexibly access Map values, supporting the "." (dot) syntax for
* accessing sub-map values and the "[]" (square bracket) syntax for accessing
* list elements. See individual Map operations for more information.
*/
@SourceMonitored
@SuppressWarnings("serial")
public final class FlexibleMapAccessor<T> implements Serializable, IsEmpty {
public static final String module = FlexibleMapAccessor.class.getName();
private static final Cache<String, FlexibleMapAccessor<?>> fmaCache = UtilCache.createUtilCache("flexibleMapAccessor.ExpressionCache");
@SuppressWarnings("unchecked")
private static final FlexibleMapAccessor nullFma = new FlexibleMapAccessor("");
private final boolean isEmpty;
private final String original;
private final String bracketedOriginal;
private final FlexibleStringExpander fse;
private final boolean isAscending;
private FlexibleMapAccessor(String name) {
this.original = name;
this.isEmpty = name.isEmpty();
FlexibleStringExpander fse = null;
String bracketedOriginal = null;
boolean isAscending = true;
if (UtilValidate.isNotEmpty(name)) {
if (name.charAt(0) == '-') {
isAscending = false;
name = name.substring(1);
} else if (name.charAt(0) == '+') {
isAscending = true;
name = name.substring(1);
}
if (name.contains(FlexibleStringExpander.openBracket)) {
fse = FlexibleStringExpander.getInstance(name);
} else {
bracketedOriginal = FlexibleStringExpander.openBracket.concat(UelUtil.prepareExpression(name).concat(FlexibleStringExpander.closeBracket));
}
}
this.bracketedOriginal = bracketedOriginal;
this.isAscending = isAscending;
this.fse = fse;
if (Debug.verboseOn()) {
Debug.logVerbose("FlexibleMapAccessor created, original = " + this.original, module);
}
}
/** Returns a FlexibleMapAccessor instance.
* @param original The original String expression
* @return A FlexibleMapAccessor instance
*/
@SuppressWarnings("unchecked")
public static <T> FlexibleMapAccessor<T> getInstance(String original) {
if (UtilValidate.isEmpty(original) || "null".equals(original)) {
return nullFma;
}
FlexibleMapAccessor fma = fmaCache.get(original);
if (fma == null) {
fmaCache.putIfAbsent(original, new FlexibleMapAccessor(original));
fma = fmaCache.get(original);
}
return fma;
}
/**
* Returns <code>true</code> if this <code>FlexibleMapAccessor</code> contains a nested expression.
* @return <code>true</code> if this <code>FlexibleMapAccessor</code> contains a nested expression
*/
public boolean containsNestedExpression() {
return fse != null;
}
public String getOriginalName() {
return this.original;
}
public boolean getIsAscending() {
return this.isAscending;
}
public boolean isEmpty() {
return this.isEmpty;
}
/** Given the name based information in this accessor, get the value from the passed in Map.
* Supports LocalizedMaps by getting a String or Locale object from the base Map with the key "locale", or by explicit locale parameter.
* @param base
* @return the found value
*/
public T get(Map<String, ? extends Object> base) {
return get(base, null);
}
/** Given the name based information in this accessor, get the value from the passed in Map.
* Supports LocalizedMaps by getting a String or Locale object from the base Map with the key "locale", or by explicit locale parameter.
* Note that the localization functionality is only used when the lowest level sub-map implements the LocalizedMap interface
* @param base Map to get value from
* @param locale Optional locale parameter, if null will see if the base Map contains a "locale" key
* @return the found value
*/
@SuppressWarnings("unchecked")
public T get(Map<String, ? extends Object> base, Locale locale) {
if (base == null || this.isEmpty) {
return null;
}
if (locale != null && !base.containsKey(UelUtil.localizedMapLocaleKey)) {
// This method is a hot spot, so placing the cast here instead of in another class.
// Map<String, Object> writableMap = UtilGenerics.cast(base);
Map<String, Object> writableMap = (Map<String, Object>) base;
writableMap.put(UelUtil.localizedMapLocaleKey, locale);
}
Object obj = null;
try {
obj = UelUtil.evaluate(base, getExpression(base));
} catch (PropertyNotFoundException e) {
// PropertyNotFound exceptions are common, so log verbose.
if (Debug.verboseOn()) {
Debug.logVerbose("UEL exception while getting value: " + e + ", original = " + this.original, module);
}
} catch (Exception e) {
Debug.logError("UEL exception while getting value: " + e + ", original = " + this.original, module);
}
// This method is a hot spot, so placing the cast here instead of in another class.
// return UtilGenerics.<T>cast(obj);
return (T) obj;
}
/** Given the name based information in this accessor, put the value in the passed in Map.
* If the brackets for a list are empty the value will be appended to the list,
* otherwise the value will be set in the position of the number in the brackets.
* If a "+" (plus sign) is included inside the square brackets before the index
* number the value will inserted/added at that point instead of set at the point.
* @param base
* @param value
*/
public void put(Map<String, Object> base, T value) {
if (this.isEmpty) {
return;
}
if (base == null) {
throw new IllegalArgumentException("Cannot put a value in a null base Map");
}
try {
UelUtil.setValue(base, getExpression(base), value == null ? Object.class : value.getClass(), value);
} catch (Exception e) {
Debug.logError("UEL exception while setting value: " + e + ", original = " + this.original, module);
}
}
/** Given the name based information in this accessor, remove the value from the passed in Map.
* @param base the Map to remove from
* @return the object removed
*/
public T remove(Map<String, ? extends Object> base) {
if (this.isEmpty) {
return null;
}
T object = get(base);
if (object == null) {
return null;
}
try {
Map<String, Object> writableMap = UtilGenerics.cast(base);
UelUtil.removeValue(writableMap, getExpression(base));
} catch (Exception e) {
Debug.logError("UEL exception while removing value: " + e + ", original = " + this.original, module);
}
return object;
}
private String getExpression(Map<String, ? extends Object> base) {
String expression = null;
if (this.fse != null) {
expression = FlexibleStringExpander.openBracket.concat(UelUtil.prepareExpression(this.fse.expandString(base)).concat(FlexibleStringExpander.closeBracket));
} else {
expression = this.bracketedOriginal;
}
return expression;
}
@Override
public String toString() {
return this.original;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
try {
FlexibleMapAccessor that = (FlexibleMapAccessor) obj;
return UtilObject.equalsHelper(this.original, that.original);
} catch (Exception e) {}
return false;
}
@Override
public int hashCode() {
return this.original.hashCode();
}
}