001/*
002 * Copyright (C) 2014 Jörg Prante
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.xbib.elasticsearch.plugin.jdbc.util;
017
018import org.elasticsearch.common.xcontent.ToXContent;
019import org.elasticsearch.common.xcontent.XContentBuilder;
020
021import java.io.IOException;
022
023/**
024 * This class represents one or many values. Each value is represented at most
025 * once. New values are appended.
026 */
027public class Values<O extends Object> implements ToXContent {
028
029    /**
030     * The values are implemented as an object array
031     */
032    @SuppressWarnings({"unchecked"})
033    private O[] values = (O[]) new Object[0];
034
035    /**
036     * Create new Values from an existing values by appending a value.
037     *
038     * @param old      existing values or null if values should be created
039     * @param value    a new value
040     * @param sequence true if value should be splitted by commas to multiple values
041     */
042    @SuppressWarnings({"unchecked"})
043    public Values(Object old, O value, boolean sequence) {
044        if (old instanceof Values) {
045            O[] vals = (O[]) ((Values) old).getValues();
046            if (vals != null) {
047                this.values = vals;
048            }
049        }
050        O[] newValues;
051        if (sequence && value != null) {
052            newValues = (O[]) value.toString().split(",");
053        } else if (value instanceof Object[]) {
054            newValues = (O[]) value;
055        } else {
056            newValues = (O[]) new Object[]{value};
057        }
058        for (O v : newValues) {
059            addValue(v);
060        }
061    }
062
063    /**
064     * Append value to existing values.
065     *
066     * @param v the value to add
067     */
068    @SuppressWarnings({"unchecked"})
069    public void addValue(O v) {
070        int l = this.values.length;
071        for (O aValue : this.values) {
072            if (v != null && v.equals(aValue)) {
073                //value already found, do nothing
074                return;
075            }
076        }
077        if (l == 0) {
078            this.values = (O[]) new Object[]{v};
079        } else {
080            // never add a null value to an existing list of values
081            if (v != null) {
082                if (l == 1 && this.values[0] == null) {
083                    // if there's one existing value and it's null, replace it with the new one
084                    this.values = (O[]) new Object[]{v};
085                } else {
086                    // otherwise copy the existing value(s) and add the new one
087                    O[] oldValues = this.values;
088                    this.values = (O[]) new Object[l + 1];
089                    System.arraycopy(oldValues, 0, this.values, 0, l);
090                    this.values[l] = v;
091                }
092            }
093        }
094    }
095
096    /**
097     * Get the values.
098     *
099     * @return the values
100     */
101    public O[] getValues() {
102        return this.values;
103    }
104
105    public boolean isNull() {
106        return this.values.length == 0 || (this.values.length == 1 && this.values[0] == null);
107    }
108
109    @Override
110    public String toString() {
111        StringBuilder sb = new StringBuilder();
112        if (this.values.length > 1) {
113            sb.append('[');
114        }
115        for (int i = 0; i < this.values.length; i++) {
116            if (i > 0) {
117                sb.append(',');
118            }
119            sb.append(format(this.values[i]));
120        }
121        if (this.values.length > 1) {
122            sb.append(']');
123        }
124        return sb.toString();
125    }
126
127    /**
128     * Format a value for JSON.
129     *
130     * @param o the value
131     * @return the formtted value
132     */
133    private String format(Object o) {
134        if (o == null) {
135            return "null";
136        }
137        if (o instanceof Integer) {
138            return Integer.toString((Integer) o);
139        }
140        if (o instanceof Long) {
141            return Long.toString((Long) o);
142        }
143        if (o instanceof Boolean) {
144            return Boolean.toString((Boolean) o);
145        }
146        if (o instanceof Float) {
147            // suppress scientific notation
148            return String.format("%.12f", (Float) o);
149        }
150        if (o instanceof Double) {
151            // suppress scientific notation
152            return String.format("%.12f", (Double) o);
153        }
154        // stringify
155        String t = o.toString();
156        t = t.replaceAll("\"", "\\\"");
157        return '"' + t + '"';
158    }
159
160    @Override
161    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
162        if (this.values.length == 0) {
163            builder.nullValue();
164            return builder;
165        }
166        if (this.values.length > 1) {
167            builder.startArray();
168        }
169        for (O aValue : this.values) {
170            builder.value(aValue);
171        }
172        if (this.values.length > 1) {
173            builder.endArray();
174        }
175        return builder;
176    }
177}