Динамическое создание графического интерфейса Swing из схемы JSON (с использованием Metawidget)
Как следует из названия, я хотел бы создать графический интерфейс Swing на основе схемы JSON (которую я получаю в реальном времени) и использовать ее для заполнения объекта JSONObject (Google SimpleJSON). Я думал использовать для этого фреймворк Metawidget, но пока безуспешно. Я нашел различные ссылки в Интернете, но, похоже, ни одна из них не работает в этом конкретном случае. Всегда отсутствуют некоторые классы или методы, которые используются в примере, а документация Metawidget не очень хороша (по крайней мере, мне не удалось найти набор примеров для версии 4.2). Схема JSON, которую я получаю, описывает классы Java, которые были описаны с помощью JSONSchema Джексона на стороне сервера, но недоступны локально или могут быть известны заранее, поэтому с этим также следует справиться.
Есть ли у кого-нибудь предложения по поводу другого подхода или несколько примеров / ссылок, которые я могу использовать? Конечно, более приветствуется и конкретный код, который компилируется с Metawidget 4.2.
--- РЕДАКТИРОВАТЬ --- (Из-за ответа Ричарда Кеннарда)
Используя предоставленные блоки кода, мне удалось создать графический интерфейс. Однако мне нужно было изменить значение строки json и jsonSchema, вставить дополнительные значения и изменить порядок инспекторов, переданных в CompositeInspector. Вот код и сгенерированный графический интерфейс:
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
String json = "{\"person\": { \"firstname\": \"Richard\", \"surname\": \"Kennard\", \"notes\": \"Software developer\" }}";
String jsonSchema = "{ \"name\": \"person\", \"type\": \"person\", properties: { \"firstname\": { \"required\": true }, \"surname\": { \"required\": true }, \"notes\": { \"large\": true }}}";
final SwingMetawidget metawidget = new SwingMetawidget();
metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
new JsonSchemaInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ) ) ),
new JsonInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( json.getBytes() ) ) )
)));
metawidget.setToInspect( json );
frame.add( metawidget, BorderLayout.CENTER );
frame.setSize(500, 500);
frame.setVisible(true);
Это без использования MapWidgetProcessor, потому что (я полагаю) его нужно изменить для поддержки преобразования String в JSONObject. (Кроме того, переменная 'NAME' в этом блоке кода не определена и, предположительно, должна быть заменена на 'elementName'?)
Однако все это вызывает пару новых вопросов:
1) Почему значения из json не отображаются на компоненты?
2) Какой должна быть настройка, если у меня не было значения json, а только jsonShema?
3) Почему не работает код при явном указании типа свойства в схеме, например:
"firstname": {"required": true, "type": "string"}
2 ответа
Основной принцип Metawidget - это возможность сочетать и сочетать различные подходы в соответствии с вашей архитектурой. Так что я могу ответить на этот вопрос по частям.
Базовый SwingMetawidget:
// UI
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// Metawidget
final SwingMetawidget metawidget = new SwingMetawidget();
...configure Metawidget by setting inspectors, inspection result processors, widget builders, etc...
metawidget.setToInspect( myData );
frame.add( metawidget, BorderLayout.CENTER );
Чтобы прочитать данные типа JSON и схемы JSON, используйте CompositeInspector:
String json = "{ \"firstname\": \"Richard\", \"surname\": \"Kennard\", \"notes\": \"Software developer\" }";
String jsonSchema = "{ properties: { \"firstname\": { \"required\": true }, \"notes\": { \"large\": true }}}";
...
metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
new JsonInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( json.getBytes() ) ) ),
new JsonSchemaInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ) ) ) )
Чтобы сопоставить типы, рассмотрите возможность добавления в TypeMappingInspectionResultProcessor:
metawidget.addInspectionResultProcessor(
new TypeMappingInspectionResultProcessor<SwingMetawidget>(
new TypeMappingInspectionResultProcessorConfig()
.setTypeMapping( "foo", "bar" )
.setTypeMapping( "abc", "def" )));
Или, возможно, лучший подход, добавьте собственный WidgetBuilder для обработки виджетов для ваших неизвестных типов:
metawidget.setWidgetBuilder( new CompositeWidetBuilder( new ompositeWidgetBuilderConfig()
.setWidgetBuilders(
new OverriddenWidgetBuilder(), new ReadOnlyWidgetBuilder(),
new MyWidgetBuilder(), new SwingWidgetBuilder()
)));
где MyWidgetBuilder делает что-то вроде
class MyWidgetBuilder
implements WidgetBuilder<JComponent, SwingMetawidget> {
public JComponent buildWidget( String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
if ( "my.special.type".equals( attributes.get( TYPE ) ) )
return new JSuperWidget();
}
// Fall through to other WidgetBuilder
return null;
}
По умолчанию JComponents никуда не сохраняют свои данные. Для этого вам нужно добавить что-то вроде BeansBindingProcessor. Конечно, BeansBinding привязывается только к JavaBeans. Если вы хотите привязаться к чему-то еще (например, к карте JSON), вы можете добавить свой собственный MapWidgetProcessor:
/**
* MapWidgetProcessor uses the Metawidget's <code>toInspect</code> to retrieve/store values.
*/
public class MapWidgetProcessor
implements AdvancedWidgetProcessor<JComponent, SwingMetawidget> {
//
// Public methods
//
@Override
public void onStartBuild( SwingMetawidget metawidget ) {
getWrittenComponents( metawidget ).clear();
}
/**
* Retrieve the values from the Map and put them in the Components.
*/
@Override
public JComponent processWidget( JComponent component, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
String attributeName = attributes.get( NAME );
getWrittenComponents( metawidget ).put( attributeName, component );
// Fetch the value...
Map<String, Object> toInspect = metawidget.getToInspect();
Object value = toInspect.get( attributeName );
if ( value == null ) {
return component;
}
// ...and apply it to the component. For simplicity, we won't worry about converters
String componentProperty = metawidget.getValueProperty( component );
ClassUtils.setProperty( component, componentProperty, value );
return component;
}
@Override
public void onEndBuild( SwingMetawidget metawidget ) {
// Do nothing
}
/**
* Store the values from the Components back into the Map.
*/
public void save( SwingMetawidget metawidget ) {
Map<String, Object> toInspect = metawidget.getToInspect();
for ( Map.Entry<String,JComponent> entry : getWrittenComponents( metawidget ).entrySet() ) {
JComponent component = entry.getValue();
String componentProperty = metawidget.getValueProperty( component );
Object value = ClassUtils.getProperty( component, componentProperty );
toInspect.put( entry.getKey(), value );
}
}
//
// Private methods
//
/**
* During load-time we keep track of all the components. At save-time we write them all back
* again.
*/
private Map<String,JComponent> getWrittenComponents( SwingMetawidget metawidget ) {
@SuppressWarnings( "unchecked" )
Map<String,JComponent> writtenComponents = (Map<String,JComponent>) metawidget.getClientProperty( MapWidgetProcessor.class );
if ( writtenComponents == null ) {
writtenComponents = CollectionUtils.newHashMap();
metawidget.putClientProperty( MapWidgetProcessor.class, writtenComponents );
}
return writtenComponents;
}
}
В ответ на новые вопросы:
2) Затем вам нужно будет предоставить полную схему. На данный момент jsonSchema имеет только такие атрибуты, как "требуется". Такие атрибуты, как "тип", выводятся из значений объекта json. CompositeInspector объединяет их для вас. Но если вы хотите просто иметь JsonSchemaInspector (без JsonInspector, без CompositeInspector), тогда ваш jsonSchema должен иметь все атрибуты
3) Потому что "строка" - это тип JavaScript. Эквивалент Java - java.lang.String. Таким образом, вы можете использовать TypeMappingInspectionResultProcessor (или его подкласс JsonSchemaMappingInspectionResultProcessor). Это может показаться обременительным, но помните, что то, что вы делаете, довольно необычно (рендеринг JSON на Java). К счастью, Metawidget поддерживает всевозможные комбинации.
Наконец: "поддержка преобразования String в JSONObject" - я бы так не подумал. JSONObject - это концепция верхнего уровня. По сути, это карта. Отдельные поля, к которым необходимо выполнить привязку, по-прежнему являются примитивами (строки, числа и т. Д.). Так что MapWidgetProcessor, вероятно, подойдет.