/* * 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.apache.felix.metatype; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.felix.metatype.internal.Activator; import org.osgi.service.log.LogService; import org.osgi.service.metatype.AttributeDefinition; /** * The <code>AD</code> class represents the <code>AD</code> element of the * meta type descriptor. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class AD extends OptionalAttributes { /** * The message returned from the {@link #validate(String)} method if the * value is not any of the specified {@link #getOptionValues() option values} * (value is "%not a valid option"). */ public static final String VALIDATE_NOT_A_VALID_OPTION = "%not a valid option"; /** * The message returned from the {@link #validate(String)} method if the * value is invalid considering its type (value is "%invalid value"). */ public static final String VALIDATE_INVALID_VALUE = "%invalid value"; /** * The message returned from the {@link #validate(String)} method if the * value is greater than the specified {@link #getMax() maximum value} * (value is "%greater than maximum"). */ public static final String VALIDATE_GREATER_THAN_MAXIMUM = "%greater than maximum"; /** * The message returned from the {@link #validate(String)} method if the * value is less than the specified {@link #getMin() minimum value} * (value is "%less than minimum"). */ public static final String VALIDATE_LESS_THAN_MINIMUM = "%less than minimum"; /** * The message returned from the {@link #validate(String)} method if the * value is null or cannot be converted to an attribute value and a value * is {@link #isRequired() required} (value is "%missing required value"). */ public static final String VALIDATE_MISSING = "%missing required value"; private String id; private String name; private String description; private int type; private int cardinality = 0; private String[] optionLabels; private String[] optionValues; private String[] defaultValue; private String min; private String max; private boolean isRequired = true; public String getID() { return id; } public String getName() { return name; } public String getDescription() { return description; } public int getType() { return type; } public int getCardinality() { return cardinality; } public String[] getOptionLabels() { return optionLabels; } public String[] getOptionValues() { return optionValues; } public String[] getDefaultValue() { return defaultValue; } public String getMin() { return min; } public String getMax() { return max; } public boolean isRequired() { return isRequired; } /** * Implements validation of the <code>valueString</code> and returns an * indication of the validation result. * * @param valueString The string representation of the value to validate, * can be <code>null</code>. * * @return <code>null</code> if no validation is performed, <tt>""</tt> if * the value is accepted as valid, or a non-empty string * indicating a validation problem was found. * * @see ADValidator#validate(AD, String) * @see #VALIDATE_GREATER_THAN_MAXIMUM * @see #VALIDATE_NOT_A_VALID_OPTION * @see #VALIDATE_LESS_THAN_MINIMUM * @see #VALIDATE_INVALID_VALUE * @see #VALIDATE_MISSING */ public String validate(String valueString) { return ADValidator.validate(this, valueString); } //--------- Setters for setting up this instance -------------------------- /** * @param id the id to set */ public void setID(String id) { this.id = id; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @param description the description to set */ public void setDescription(String description) { this.description = description; } /** * @param typeString the type to set */ public void setType(String typeString) { this.type = toType(typeString); } /** * @param cardinality the cardinality to set */ public void setCardinality(int cardinality) { this.cardinality = cardinality; } /** * @param options the options to set */ public void setOptions(Map options) { optionLabels = new String[options.size()]; optionValues = new String[options.size()]; int i = 0; for (Iterator oi = options.entrySet().iterator(); oi.hasNext(); i++) { Map.Entry entry = (Map.Entry) oi.next(); optionValues[i] = String.valueOf(entry.getKey()); optionLabels[i] = String.valueOf(entry.getValue()); } } /** * Sets the default value(s) for this AD. * <p> * NOTE: this method is depending on the value of {@link #getCardinality()}! Make sure that the * cardinality is properly set <b>before</b> calling this method. * </p> * * @param defaultValue the default value to set, as encoded string-value (using comma's as separator), can be <code>null</code>. */ public void setDefaultValue(String defaultValue) { setDefaultValue(splitList(defaultValue), Math.abs(this.cardinality)); } /** * @param min the min to set */ public void setMin(String min) { this.min = min; } /** * @param max the max to set */ public void setMax(String max) { this.max = max; } /** * @param isRequired the isRequired to set */ public void setRequired(boolean isRequired) { this.isRequired = isRequired; } public static int toType(String typeString) { if ("String".equals(typeString)) { return AttributeDefinition.STRING; } else if ("Long".equals(typeString)) { return AttributeDefinition.LONG; } else if ("Double".equals(typeString)) { return AttributeDefinition.DOUBLE; } else if ("Float".equals(typeString)) { return AttributeDefinition.FLOAT; } else if ("Integer".equals(typeString)) { return AttributeDefinition.INTEGER; } else if ("Byte".equals(typeString)) { return AttributeDefinition.BYTE; } else if ("Character".equals(typeString) || "Char".equals(typeString)) { return AttributeDefinition.CHARACTER; } else if ("Boolean".equals(typeString)) { return AttributeDefinition.BOOLEAN; } else if ("Short".equals(typeString)) { return AttributeDefinition.SHORT; } else if ("Password".equals(typeString)) { return AttributeDefinition.PASSWORD; } // finally fall back to string for illegal values return AttributeDefinition.STRING; } public static String[] splitList(String listString) { if (listString == null) { return null; } else if (listString.length() == 0) { return new String[] { "" }; } List strings = new ArrayList(); StringBuffer sb = new StringBuffer(); int length = listString.length(); boolean escaped = false; int spaceCount = 0; boolean start = true; for (int i = 0; i < length; i++) { char ch = listString.charAt(i); final boolean isWhitespace = Character.isWhitespace(ch); if (start) { if (isWhitespace) { continue; } start = false; } if (ch == '\\') { if (!escaped) { escaped = true; continue; } } else if (ch == ',') { if (!escaped) { // unescaped comma, this is a string delimiter... strings.add(sb.toString()); sb.setLength(0); start = true; spaceCount = 0; continue; } } else if (ch == ' ') { // space is only ignored at beginning and end but not if escaped if (!escaped) { spaceCount++; continue; } } else if (isWhitespace) { // Other whitespaces are ignored... continue; } if (spaceCount > 0) { for (int m = 0; m < spaceCount; m++) { sb.append(" "); } spaceCount = 0; } sb.append(ch); escaped = false; } // Always add the last string, as it contains everything after the last comma... strings.add(sb.toString()); return (String[]) strings.toArray(new String[strings.size()]); } protected Comparable convertToType(final String value) { if (value != null && value.length() > 0) { try { switch (getType()) { case AttributeDefinition.BOOLEAN: // Boolean is only Comparable starting with Java 5 return new ComparableBoolean(value); case AttributeDefinition.CHARACTER: return new Character(value.charAt(0)); case AttributeDefinition.BYTE: return Byte.valueOf(value); case AttributeDefinition.SHORT: return Short.valueOf(value); case AttributeDefinition.INTEGER: return Integer.valueOf(value); case AttributeDefinition.LONG: return Long.valueOf(value); case AttributeDefinition.FLOAT: return Float.valueOf(value); case AttributeDefinition.DOUBLE: return Double.valueOf(value); case AttributeDefinition.STRING: case AttributeDefinition.PASSWORD: default: return value; } } catch (NumberFormatException nfe) { Activator.log(LogService.LOG_INFO, "Cannot convert value '" + value + "'", nfe); } } return null; } /** * @param values the defaultValue to set */ protected void setDefaultValue(String[] values, int cardinality) { if (values != null) { int count = 0; int max = Math.min(values.length, Math.max(1, cardinality)); for (int i = 0; count < max && i < values.length; i++) { if ("".equals(ADValidator.validate(this, values[i]))) { count++; } else { values[i] = null; } } if (count == 0) { values = cardinality == 0 ? null : new String[0]; } else if (count != values.length) { String[] filterValues = new String[count]; int index = 0; for (int i = 0; index < count && i < values.length; i++) { if (values[i] != null) { filterValues[index] = values[i]; index++; } } values = filterValues; } } this.defaultValue = values; } private static class ComparableBoolean implements Comparable { private boolean value; ComparableBoolean(String boolValue) { value = Boolean.valueOf(boolValue).booleanValue(); } public int compareTo(Object obj) { ComparableBoolean cb = (ComparableBoolean) obj; return (cb.value == value ? 0 : (value ? 1 : -1)); } } }