/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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.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 io.datakernel.jmx;
import javax.management.openmbean.*;
import java.lang.reflect.Array;
import java.util.*;
import static io.datakernel.util.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
final class AttributeNodeForList extends AttributeNodeForLeafAbstract {
private final AttributeNode subNode;
private final ArrayType<?> arrayType;
private final boolean isListOfJmxRefreshables;
public AttributeNodeForList(String name, String description, boolean visible, ValueFetcher fetcher, AttributeNode subNode,
boolean isListOfJmxRefreshables) {
super(name, description, fetcher, visible);
checkArgument(!name.isEmpty(), "List attribute cannot have empty name");
this.subNode = subNode;
this.arrayType = createArrayType(subNode, name);
this.isListOfJmxRefreshables = isListOfJmxRefreshables;
}
private static ArrayType<?> createArrayType(AttributeNode subNode, String name) {
String nodeName = "Attribute name = " + name;
Set<String> visibleAttrs = subNode.getVisibleAttributes();
Map<String, OpenType<?>> attrTypes = subNode.getOpenTypes();
if (visibleAttrs.size() == 0) {
throw new IllegalArgumentException("Arrays must have at least one visible attribute. " + nodeName);
}
try {
OpenType<?> elementType;
if (visibleAttrs.size() == 1) {
String attr = visibleAttrs.iterator().next();
OpenType<?> openType = attrTypes.get(attr);
// TODO(vmykhalko): check this case
if (openType instanceof ArrayType) {
throw new IllegalArgumentException("Multidimensional arrays are note supported in JMX. " + nodeName);
}
elementType = openType;
} else {
List<String> itemNames = new ArrayList<>();
List<OpenType<?>> itemTypes = new ArrayList<>();
for (String visibleAttr : visibleAttrs) {
OpenType<?> visibleAttrType = attrTypes.get(visibleAttr);
itemNames.add(visibleAttr);
itemTypes.add(visibleAttrType);
}
String[] itemNamesArr = itemNames.toArray(new String[itemNames.size()]);
OpenType<?>[] itemTypesArr = itemTypes.toArray(new OpenType<?>[itemTypes.size()]);
elementType =
new CompositeType("CompositeData", "CompositeData", itemNamesArr, itemNamesArr, itemTypesArr);
}
return new ArrayType<Object>(1, elementType);
} catch (OpenDataException e) {
throw new IllegalArgumentException("Cannot create ArrayType. " + nodeName, e);
}
}
@Override
public Map<String, OpenType<?>> getOpenTypes() {
return Collections.<String, OpenType<?>>singletonMap(name, arrayType);
}
@Override
public Object aggregateAttribute(String attrName, List<?> sources) {
List<Map<String, Object>> attributesFromAllElements = new ArrayList<>();
Set<String> visibleSubAttrs = subNode.getVisibleAttributes();
for (Object source : sources) {
List<?> currentList = (List<?>) fetcher.fetchFrom(source);
if (currentList != null) {
for (Object element : currentList) {
Map<String, Object> attributesFromElement =
subNode.aggregateAttributes(visibleSubAttrs, singletonList(element));
attributesFromAllElements.add(attributesFromElement);
}
}
}
return attributesFromAllElements.size() > 0 ? createArrayFrom(attributesFromAllElements) : null;
}
private Object[] createArrayFrom(List<Map<String, Object>> attributesFromAllElements) {
OpenType<?> arrayElementOpenType = arrayType.getElementOpenType();
if (arrayElementOpenType instanceof ArrayType) {
throw new RuntimeException("Multidimensional arrays are not supported");
} else {
try {
Class<?> elementClass = classOf(arrayType.getElementOpenType());
Object[] array = (Object[]) Array.newInstance(elementClass, attributesFromAllElements.size());
for (int i = 0; i < attributesFromAllElements.size(); i++) {
Map<String, Object> attributesFromElement = attributesFromAllElements.get(i);
array[i] = jmxCompatibleObjectOf(arrayElementOpenType, attributesFromElement);
}
return array;
} catch (OpenDataException e) {
throw new RuntimeException(e);
}
}
}
private static Object jmxCompatibleObjectOf(OpenType<?> openType, Map<String, Object> attributes)
throws OpenDataException {
if (openType instanceof SimpleType || openType instanceof TabularType) {
checkArgument(attributes.size() == 1);
return attributes.values().iterator().next();
} else if (openType instanceof CompositeType) {
CompositeType compositeType = (CompositeType) openType;
return new CompositeDataSupport(compositeType, attributes);
} else {
throw new RuntimeException("There is no support for " + openType);
}
}
@Override
@SuppressWarnings("unchecked")
public List<JmxRefreshable> getAllRefreshables(final Object source) {
if (!isListOfJmxRefreshables) {
return emptyList();
}
final List<JmxRefreshable> listRef = (List<JmxRefreshable>) fetcher.fetchFrom(source);
return Collections.<JmxRefreshable>singletonList(new JmxRefreshable() {
@Override
public void refresh(long timestamp) {
for (JmxRefreshable jmxRefreshableElement : listRef) {
jmxRefreshableElement.refresh(timestamp);
}
}
});
}
@Override
public boolean isSettable(String attrName) {
return false;
}
@Override
public void setAttribute(String attrName, Object value, List<?> targets) {
throw new UnsupportedOperationException();
}
private static Class<?> classOf(OpenType<?> openType) {
if (openType.equals(SimpleType.BOOLEAN)) {
return Boolean.class;
} else if (openType.equals(SimpleType.BYTE)) {
return Byte.class;
} else if (openType.equals(SimpleType.SHORT)) {
return Short.class;
} else if (openType.equals(SimpleType.CHARACTER)) {
return Character.class;
} else if (openType.equals(SimpleType.INTEGER)) {
return Integer.class;
} else if (openType.equals(SimpleType.LONG)) {
return Long.class;
} else if (openType.equals(SimpleType.FLOAT)) {
return Float.class;
} else if (openType.equals(SimpleType.DOUBLE)) {
return Double.class;
} else if (openType.equals(SimpleType.STRING)) {
return String.class;
} else if (openType instanceof CompositeType) {
return CompositeData.class;
} else if (openType instanceof TabularType) {
return TabularData.class;
}
// ArrayType is not supported
throw new IllegalArgumentException(format("OpenType \"%s\" cannot be converted to Class", openType));
}
}