| /**************************************************************************** |
| * Copyright (c) 2020 Composent, Inc. and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * Composent, Inc. - initial API and implementation |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *****************************************************************************/ |
| package org.eclipse.ecf.osgi.services.remoteserviceadmin; |
| |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.UUID; |
| |
| import org.eclipse.ecf.internal.osgi.services.remoteserviceadmin.DebugOptions; |
| import org.eclipse.ecf.internal.osgi.services.remoteserviceadmin.LogUtility; |
| |
| /** |
| * Class to represent EDEF properties for load from .properties file (via {@link #loadEDEFProperties(InputStream)} or |
| * {@link #loadEDEFProperties(Reader)}) or via store to .properties file (via {@link #storeEDEFProperties(BufferedWriter, String)} |
| * or {@link #storeEDEFProperties(Writer, String)}. This class is used by the EndpointDescriptionLocator |
| * class to load from default.properties files as well as properties edeffile.properties to override |
| * the values from the edeffile.xml files specified by the Remote-Service header in manifest as per the |
| * RSA specification (chap 122 in compendium spec). |
| * @since 4.8 |
| * |
| */ |
| public class EDEFProperties extends Properties { |
| |
| private static final long serialVersionUID = -7351470248095230347L; |
| private static int nextInt = 0; |
| private static long nextLong = 0; |
| private static short nextShort = 0; |
| private static byte nextByte = 0; |
| |
| public class EDEFPropertiesValue { |
| private String type1 = "string"; //$NON-NLS-1$ |
| private String type2 = "string"; //$NON-NLS-1$ |
| private String valueString; |
| private Object valueObject; |
| |
| boolean isArray() { |
| return this.type1.equalsIgnoreCase("array"); //$NON-NLS-1$ |
| } |
| |
| boolean isSet() { |
| return this.type1.equalsIgnoreCase("set"); //$NON-NLS-1$ |
| } |
| |
| boolean isList() { |
| return this.type1.equalsIgnoreCase("list"); //$NON-NLS-1$ |
| } |
| |
| boolean isCollection() { |
| return isSet() || isList(); |
| } |
| |
| boolean isSimpleType() { |
| return !isCollection() && !isArray(); |
| } |
| |
| public boolean hasTypeAgreement(EDEFPropertiesValue otherValue) { |
| String otherType = (otherValue.isArray() || otherValue.isCollection()) ? otherValue.type2 |
| : otherValue.type1; |
| return (isArray() || isCollection()) ? this.type2.equalsIgnoreCase(otherType) |
| : this.type1.equalsIgnoreCase(otherType); |
| } |
| |
| EDEFPropertiesValue addPropertyValue(EDEFPropertiesValue newValue) { |
| if (!hasTypeAgreement(newValue)) { |
| LogUtility.logError("addEDEFPropertyValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$ |
| "type disagreement between property values for old type1=" + this.type1 + ",type2=" //$NON-NLS-1$ //$NON-NLS-2$ |
| + this.type2 + ", and new type1=" + newValue.type1 + ",type2=" + newValue.type2); //$NON-NLS-1$ //$NON-NLS-2$ |
| return this; |
| } |
| // get old and new values |
| Object ov = this.getValueObject(); |
| Object nv = newValue.getValueObject(); |
| // If we've got a collection already |
| if (isCollection()) { |
| Collection oldVs = (Collection) ov; |
| // If newValue is also collection |
| if (newValue.isCollection()) { |
| // Then addAll |
| oldVs.addAll((Collection) nv); |
| } else if (newValue.isSimpleType()) { |
| // Add single element |
| oldVs.add(nv); |
| } |
| } else if (isArray()) { |
| // Get old length |
| int oldLength = Array.getLength(ov); |
| Object newResultValue = null; |
| if (newValue.isArray()) { |
| // new value is also array...get length |
| int newLength = Array.getLength(nv); |
| // Check to make sure that the type of elements is same (type2 for arrays) |
| newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + newLength); |
| // copy ov contents to newResultValue |
| System.arraycopy(ov, 0, newResultValue, 0, oldLength); |
| // append nv contents to newResultValue after ov contents |
| System.arraycopy(nv, 0, newResultValue, oldLength, newLength); |
| } else if (newValue.isSimpleType()) { |
| newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + 1); |
| System.arraycopy(ov, 0, newResultValue, 0, oldLength); |
| Array.set(newResultValue, oldLength, nv); |
| } |
| if (newResultValue != null) { |
| this.valueObject = newResultValue; |
| } |
| } |
| return this; |
| } |
| |
| EDEFPropertiesValue(String value) { |
| // Split value with = |
| String[] valueArr = value.split("="); //$NON-NLS-1$ |
| // Split first one |
| if (valueArr.length > 1) { |
| // split second element in valueArr by : |
| String[] firstSplit = valueArr[0].split(":"); //$NON-NLS-1$ |
| // If more than one then type2 is second element in firstSplit |
| if (firstSplit.length > 1) { |
| this.type2 = firstSplit[1]; |
| } |
| // In either case type1 is firstSplit[0] |
| this.type1 = firstSplit[0]; |
| } |
| // Now set value to the last elemtn in the valueArr |
| this.valueString = valueArr[valueArr.length - 1]; |
| } |
| |
| private void setType2(Collection coll) { |
| Class<?> c = coll.iterator().next().getClass(); |
| if (!c.equals(String.class)) { |
| this.type2 = c.getSimpleName().toLowerCase(); |
| } |
| } |
| |
| EDEFPropertiesValue(Object value) { |
| Class<?> clazz = value.getClass(); |
| if (clazz.isArray()) { |
| this.type1 = "array"; //$NON-NLS-1$ |
| Class<?> compType = clazz.getComponentType(); |
| if (!compType.equals(String.class)) { |
| this.type2 = compType.getSimpleName().toLowerCase(); |
| } |
| } else if (List.class.isInstance(value)) { |
| this.type1 = "list"; //$NON-NLS-1$ |
| setType2((List) value); |
| } else if (Set.class.isInstance(value)) { |
| this.type1 = "set"; //$NON-NLS-1$ |
| setType2((Set) value); |
| } else { |
| String type = clazz.getSimpleName().toLowerCase(); |
| if (!type.equalsIgnoreCase("string")) { //$NON-NLS-1$ |
| this.type1 = type; |
| } |
| } |
| this.valueObject = value; |
| |
| } |
| |
| private Object getSimpleValue(Class<?> simpleType, Object value) { |
| try { |
| return simpleType.getDeclaredMethod("valueOf", new Class[] { String.class }).invoke(null, value); //$NON-NLS-1$ |
| } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException |
| | NoSuchMethodException | SecurityException e) { |
| LogUtility.logWarning("getSimpleValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, this.getClass(), //$NON-NLS-1$ |
| "Cannot create instance of simpleType=" + simpleType + ", value=" + value); //$NON-NLS-1$ //$NON-NLS-2$ |
| return null; |
| } |
| } |
| |
| boolean isUnique() { |
| return "unique".equals(this.type2); //$NON-NLS-1$ |
| } |
| |
| Object readSimpleValue(String simpleType, String value) { |
| switch (simpleType) { |
| case "long": //$NON-NLS-1$ |
| case "Long": //$NON-NLS-1$ |
| if ("unique".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$ |
| return getNextLong(); |
| } else if ("nanoTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$ |
| return System.nanoTime(); |
| } else if ("milliTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$ |
| return System.currentTimeMillis(); |
| } |
| return getSimpleValue(Long.class, value); |
| case "double": //$NON-NLS-1$ |
| case "Double": //$NON-NLS-1$ |
| return getSimpleValue(Double.class, value); |
| case "float": //$NON-NLS-1$ |
| case "Float": //$NON-NLS-1$ |
| return getSimpleValue(Float.class, value); |
| case "int": //$NON-NLS-1$ |
| case "integer": //$NON-NLS-1$ |
| case "Integer": //$NON-NLS-1$ |
| if ("unique".equals(this.type2)) { //$NON-NLS-1$ |
| return getNextInteger(); |
| } |
| return getSimpleValue(Integer.class, value); |
| case "Byte": //$NON-NLS-1$ |
| case "byte": //$NON-NLS-1$ |
| if ("unique".equals(this.type2)) { //$NON-NLS-1$ |
| return getNextByte(); |
| } |
| return getSimpleValue(Byte.class, value); |
| case "char": //$NON-NLS-1$ |
| case "Character": //$NON-NLS-1$ |
| return getSimpleValue(Character.class, value.toCharArray()[0]); |
| case "boolean": //$NON-NLS-1$ |
| case "Boolean": //$NON-NLS-1$ |
| return getSimpleValue(Boolean.class, value); |
| case "short": //$NON-NLS-1$ |
| case "Short": //$NON-NLS-1$ |
| if ("unique".equals(this.type2)) { //$NON-NLS-1$ |
| return getNextShort(); |
| } |
| return getSimpleValue(Short.class, value); |
| case "uuid": //$NON-NLS-1$ |
| case "Uuid": //$NON-NLS-1$ |
| case "UUID": //$NON-NLS-1$ |
| // we don't care whether 'unique' is given or not |
| return UUID.randomUUID().toString(); |
| case "String": //$NON-NLS-1$ |
| case "string": //$NON-NLS-1$ |
| return value; |
| default: |
| return null; |
| } |
| } |
| |
| Object readArrayValues(String collectionValue) { |
| String[] elements = this.valueString.split("\\s*,\\s*"); //$NON-NLS-1$ |
| Object result = null; |
| for (int i = 0; i < elements.length; i++) { |
| Object elementValue = readSimpleValue(this.type2, elements[i]); |
| if (elementValue == null) { |
| LogUtility.logWarning("getArrayValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$ |
| "array element=" + elements[i] + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$ |
| continue; |
| } else { |
| if (i == 0) { |
| result = Array.newInstance(elementValue.getClass(), elements.length); |
| } |
| Array.set(result, i, elementValue); |
| } |
| } |
| return result; |
| } |
| |
| Collection<Object> readCollectionValues(Collection<Object> c, String collectionValue) { |
| String[] elements = this.valueString.split("\\s*,\\s*"); //$NON-NLS-1$ |
| for (String element : elements) { |
| Object elementValue = readSimpleValue(this.type2, element); |
| if (elementValue == null) { |
| LogUtility.logWarning("getCollectionValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, //$NON-NLS-1$ |
| getClass(), "array element=" + element + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$ |
| continue; |
| } else { |
| c.add(elementValue); |
| } |
| } |
| return c; |
| } |
| |
| public synchronized Object getValueObject() { |
| if (this.valueObject == null) { |
| switch (this.type1) { |
| case "array": //$NON-NLS-1$ |
| this.valueObject = readArrayValues(this.valueString); |
| break; |
| case "list": //$NON-NLS-1$ |
| this.valueObject = readCollectionValues(new ArrayList(), this.valueString); |
| break; |
| case "set": //$NON-NLS-1$ |
| this.valueObject = readCollectionValues(new HashSet(), this.valueString); |
| break; |
| default: |
| this.valueObject = readSimpleValue(this.type1, this.valueString); |
| break; |
| } |
| } |
| return this.valueObject; |
| } |
| |
| public synchronized String getEDEFPropertyValueString() { |
| if (this.valueString == null) { |
| StringBuffer buf = new StringBuffer(); |
| if (isSimpleType()) { |
| if (!this.type1.equalsIgnoreCase("string")) { //$NON-NLS-1$ |
| buf.append(":").append(this.type1); //$NON-NLS-1$ |
| } |
| buf.append("=").append(this.valueObject.toString()); //$NON-NLS-1$ |
| } else { |
| buf.append(":").append(this.type1); //$NON-NLS-1$ |
| if (!this.type2.equalsIgnoreCase("string")) { //$NON-NLS-1$ |
| buf.append(":").append(this.type2); //$NON-NLS-1$ |
| } |
| buf.append("="); //$NON-NLS-1$ |
| Object[] arr = (Object[]) (isArray() ? valueObject : ((Collection) valueObject).toArray()); |
| for (int i = 0; i < arr.length; i++) { |
| buf.append(arr[i].toString()); |
| if (i != (arr.length - 1)) { |
| buf.append(","); //$NON-NLS-1$ |
| } |
| } |
| } |
| this.valueString = buf.toString(); |
| } |
| return valueString; |
| } |
| } |
| |
| synchronized static int getNextInteger() { |
| if (nextInt == Integer.MAX_VALUE) { |
| nextInt = 0; |
| } |
| return ++nextInt; |
| } |
| |
| synchronized static long getNextLong() { |
| if (nextLong == Long.MAX_VALUE) { |
| nextLong = 0; |
| } |
| return ++nextLong; |
| } |
| |
| synchronized static short getNextShort() { |
| if (nextShort == Short.MAX_VALUE) { |
| nextShort = 0; |
| } |
| return ++nextShort; |
| } |
| |
| synchronized static byte getNextByte() { |
| if (nextByte == Byte.MAX_VALUE) { |
| nextByte = 0; |
| } |
| return ++nextByte; |
| } |
| |
| /** |
| * Create empty EDEFProperties instance. |
| */ |
| public EDEFProperties() { |
| } |
| |
| /** |
| * Create EDEFProperties instance initialized with all the given properties |
| * @param properties must not be <code>null</code> |
| */ |
| public EDEFProperties(Map<String, Object> properties) { |
| putAllEDEFProperties(properties); |
| } |
| |
| @Override |
| public Object put(Object key, Object value) { |
| if (key instanceof String && value instanceof String) { |
| EDEFPropertiesValue oldValue = (EDEFPropertiesValue) this.get(key); |
| EDEFPropertiesValue newValue = new EDEFPropertiesValue((String) value); |
| return super.put(key, (oldValue == null) ? newValue : oldValue.addPropertyValue(newValue)); |
| } |
| return super.put(key, value); |
| } |
| |
| /** |
| * Put String->Object relation in as an EDEF property. Both the key and value |
| * must not be <code>null</code>. The value must be either a Set, List, Array |
| * type or a primitive type: Long/long, Byte/byte, Short/short, Int/Integer, |
| * char/Character, double/Double, float/Float. If array,set, or list, the |
| * elements must be one of the primitive types. |
| * |
| * @param key unique name/key for given value. Must not be <code>null</code>. |
| * @param value array,list,set of primitive type or instance of primitive type. |
| * @return existing Object with name/key. Null if no existing object exists. |
| */ |
| public Object putEDEFProperty(String key, Object value) { |
| if (key != null && value != null) { |
| return super.put(key, new EDEFPropertiesValue(value)); |
| } |
| return null; |
| } |
| |
| /** |
| * Get EDEF properties as String -> Object map |
| * |
| * @return Map<String,Object> containing all name->EDEFPropertiesValue contents |
| * of this EDEFProperties |
| */ |
| public Map<String, Object> getEDEFPropertiesAsMap() { |
| Map<String, Object> result = new TreeMap<String, Object>(); |
| this.forEach((k, v) -> { |
| result.put((String) k, ((EDEFPropertiesValue) v).getValueObject()); |
| }); |
| return result; |
| } |
| |
| private static void writeComments0(BufferedWriter bw, String comments) throws IOException { |
| bw.write("#"); //$NON-NLS-1$ |
| int len = comments.length(); |
| int current = 0; |
| int last = 0; |
| char[] uu = new char[6]; |
| uu[0] = '\\'; |
| uu[1] = 'u'; |
| while (current < len) { |
| char c = comments.charAt(current); |
| if (c > '\u00ff' || c == '\n' || c == '\r') { |
| if (last != current) |
| bw.write(comments.substring(last, current)); |
| if (c > '\u00ff') { |
| uu[2] = toHex((c >> 12) & 0xf); |
| uu[3] = toHex((c >> 8) & 0xf); |
| uu[4] = toHex((c >> 4) & 0xf); |
| uu[5] = toHex(c & 0xf); |
| bw.write(new String(uu)); |
| } else { |
| bw.newLine(); |
| if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') { |
| current++; |
| } |
| if (current == len - 1 |
| || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!')) |
| bw.write("#"); //$NON-NLS-1$ |
| } |
| last = current + 1; |
| } |
| current++; |
| } |
| if (last != current) |
| bw.write(comments.substring(last, current)); |
| bw.newLine(); |
| } |
| |
| private static final char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', |
| 'F' }; |
| |
| private static char toHex(int nibble) { |
| return hexDigit[(nibble & 0xF)]; |
| } |
| |
| /** |
| * Load EDEF properties from the given input stream |
| * |
| * @param ins InputStream to read the edef properties from. Must not be |
| * <code>null</code> |
| * @throws IOException if properties cannot be read from given InputStream |
| */ |
| public synchronized void loadEDEFProperties(InputStream ins) throws IOException { |
| load(ins); |
| } |
| |
| /** |
| * Load EDEF properties from the given reader |
| * |
| * @param reader Reader to read the edef properties from. Must not be |
| * <code>null</code> |
| * @throws IOException if properties cannot be read from given Reader |
| */ |
| public void loadEDEFProperties(Reader reader) throws IOException { |
| load(reader); |
| } |
| |
| /** |
| * Store EDEF properties to given Writer |
| * |
| * @param writer the Writer to write output to. Must not be <code>null</code>. |
| * @param comments |
| * @throws IOException |
| */ |
| public void storeEDEFProperties(Writer writer, String comments) throws IOException { |
| storeEDEFProperties((writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer), |
| comments); |
| } |
| |
| /** |
| * Store EDEF properties to the given buffered writer. |
| * |
| * @param bufferedWriter the BufferedWriter to write to. Must not be |
| * <code>null</code> |
| * @param comments Comment line prepended to properties file output. May |
| * be <code>null</code>. |
| * @throws IOException if properties cannot be written to bufferedWriter |
| */ |
| public void storeEDEFProperties(BufferedWriter bufferedWriter, String comments) throws IOException { |
| if (comments != null) { |
| writeComments0(bufferedWriter, comments); |
| } |
| bufferedWriter.write("#" + new Date().toString()); //$NON-NLS-1$ |
| bufferedWriter.newLine(); |
| synchronized (this) { |
| for (Enumeration<?> e = keys(); e.hasMoreElements();) { |
| Object k = e.nextElement(); |
| if (k instanceof String) { |
| String key = (String) k; |
| Object elemValue = get(key); |
| if (elemValue instanceof EDEFPropertiesValue) { |
| bufferedWriter.write(key + ((EDEFPropertiesValue) elemValue).getEDEFPropertyValueString()); |
| bufferedWriter.newLine(); |
| } |
| } |
| } |
| bufferedWriter.flush(); |
| } |
| } |
| |
| /** |
| * Put all the given properties into this map as EDEF properties, suitable for |
| * storing via {@link #storeEDEFProperties(Writer, String)} |
| * |
| * @param properties the properties to put. May not be <code>null</code> |
| */ |
| public synchronized void putAllEDEFProperties(Map<String, Object> properties) { |
| properties.forEach((k, v) -> { |
| if (k instanceof String) { |
| putEDEFProperty((String) k, v); |
| } |
| }); |
| } |
| |
| } |