/* * 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.sling.ide.eclipse.ui.views; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.jcr.PropertyType; import org.apache.sling.ide.eclipse.core.ServerUtil; import org.apache.sling.ide.eclipse.ui.nav.model.JcrNode; import org.apache.sling.ide.eclipse.ui.nav.model.JcrProperty; import org.apache.sling.ide.eclipse.ui.nav.model.JcrTextPropertyDescriptor; import org.apache.sling.ide.eclipse.ui.nav.model.ModifiableProperties; import org.apache.sling.ide.transport.NodeTypeRegistry; import org.apache.sling.ide.transport.Repository; import org.apache.sling.ide.transport.RepositoryException; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.jface.fieldassist.SimpleContentProposalProvider; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ICellEditorValidator; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.views.properties.IPropertyDescriptor; import org.eclipse.ui.views.properties.TextPropertyDescriptor; public class JcrEditingSupport extends EditingSupport { static enum ColumnId { NAME, TYPE, VALUE, MULTIPLE } private final ColumnId columnId; private final TableViewer tableViewer; private final JcrPropertiesView view; private class Field { private final Object element; Field(Object element) { this.element = element; } public boolean canEdit() { IPropertyDescriptor pd = (IPropertyDescriptor) element; Map.Entry me = (Entry) pd.getId(); if (me.getKey().equals("jcr:primaryType")) { return columnId==ColumnId.VALUE; } return true; } public Object getValue() { IPropertyDescriptor pd = (IPropertyDescriptor) element; JcrNode jcrNode = getNode(); Map.Entry me = (Entry) pd.getId(); switch(columnId) { case NAME: { return String.valueOf(me.getKey()); } case TYPE: { final int propertyType = getNode().getPropertyType(getPropertyName()); if (propertyType!=-1) { return PropertyTypeSupport.indexOfPropertyType(propertyType); } else { //TODO: otherwise hardcode to STRING return PropertyTypeSupport.indexOfPropertyType(PropertyType.STRING); } } case VALUE: { final int propertyType = getNode().getPropertyType(getPropertyName()); String rawValue = String.valueOf(me.getValue()); int index = rawValue.indexOf("}"); if (index!=-1) { rawValue = rawValue.substring(index+1); } if ((propertyType!=-1) && (propertyType==PropertyType.BOOLEAN)) { try{ if (Boolean.parseBoolean(String.valueOf(rawValue))) { return 1; } else { return 0; } } catch(Exception e) { return 0; } } else if ((propertyType!=-1) && (propertyType!=PropertyType.STRING)) { return rawValue; } return String.valueOf(me.getValue()); } case MULTIPLE: { boolean isMultiple = getNode().getProperty(getPropertyName()).isMultiple(); return isMultiple ? 1 : 0; } default: { throw new IllegalStateException("Unknown columnId: "+columnId); } } } public String getPropertyName() { IPropertyDescriptor pd = (IPropertyDescriptor) element; Map.Entry me = (Entry) pd.getId(); return String.valueOf(me.getKey()); } public void setValue(Object element, Object value) { if (getValue().equals(value)) { // then ignore this return; } JcrTextPropertyDescriptor pd = (JcrTextPropertyDescriptor) element; JcrNode jcrNode = getNode(); Map.Entry me = (Entry) pd.getId(); switch(columnId) { case NAME: { final String oldKey = String.valueOf(getValue()); final String newKey = String.valueOf(value); pd.setNewPropertyName(newKey); Map<String, String> pseudoMap = new HashMap<>(); final String propertyValue = jcrNode.getProperties().getValue(oldKey); pseudoMap.put(newKey, propertyValue); final Entry<String, String> mapEntry = pseudoMap.entrySet().iterator().next(); element = new TextPropertyDescriptor(mapEntry, propertyValue); jcrNode.renameProperty(oldKey, newKey); break; } case TYPE: { int propertyType = PropertyTypeSupport.propertyTypeOfIndex((Integer)value); jcrNode.changePropertyType(String.valueOf(me.getKey()), propertyType); break; } case VALUE: { try{ final JcrProperty property = getNode().getProperty(getPropertyName()); final int propertyType = property.getType(); String encodedValue; if (property.isMultiple()) { Object[] values = (Object[])value; encodedValue = ""; for (int i = 0; i < values.length; i++) { Object aValue = values[i]; String aValueAsString = PropertyTypeSupport.encodeValueAsString(aValue, propertyType); if (i==0) { encodedValue = aValueAsString; } else { encodedValue = encodedValue+","+aValueAsString; } } encodedValue = "["+encodedValue+"]"; } else { encodedValue = PropertyTypeSupport.encodeValueAsString(value, propertyType); } if (propertyType!=PropertyType.STRING && propertyType!=PropertyType.NAME) { encodedValue = "{"+PropertyType.nameFromValue(propertyType)+"}"+encodedValue; } jcrNode.setPropertyValue(me.getKey(), encodedValue); } catch(Exception e) { // emergency fallback jcrNode.setPropertyValue(me.getKey(), String.valueOf(value)); } break; } case MULTIPLE: { if (!(value instanceof Integer)) { // value must be an integer return; } final Integer newIsMultipleValue = (Integer) value; final boolean newIsMultiple = newIsMultipleValue==1; final JcrProperty property = getNode().getProperty(getPropertyName()); final boolean oldIsMultiple = property.isMultiple(); if (newIsMultiple==oldIsMultiple) { // then nothing is to be done return; } final String oldPropertyValue = getNode().getProperties().getValue(getPropertyName()); // split {type} prefix from value int cPos = oldPropertyValue.indexOf("}"); final String prefix; final String rawValue; if (cPos==-1) { prefix = ""; rawValue = oldPropertyValue; } else { prefix = oldPropertyValue.substring(0, cPos+1); rawValue = oldPropertyValue.substring(cPos+1); } String newValue; if (newIsMultiple) { newValue = prefix + "[" + rawValue + "]"; } else { newValue = rawValue.substring(1, rawValue.length()-1); int commaPos = newValue.indexOf(","); if (commaPos!=-1) { newValue = newValue.substring(0, commaPos); } newValue = prefix + newValue; } jcrNode.setPropertyValue(getPropertyName(), newValue); break; } } view.refreshContent(); } public int getPropertyType() { IPropertyDescriptor pd = (IPropertyDescriptor) element; Map.Entry me = (Entry) pd.getId(); String value = String.valueOf(me.getValue()); return PropertyTypeSupport.propertyTypeOfString(value); } public String getNewPropertyName() { JcrTextPropertyDescriptor pd = (JcrTextPropertyDescriptor) element; if (pd.getNewPropertyName()!=null) { return pd.getNewPropertyName(); } else { return getPropertyName(); } } } private class NewRowField extends Field { private final NewRow newRow; NewRowField(NewRow newRow) { super(newRow); this.newRow = newRow; } @Override public boolean canEdit() { return true; } @Override public int getPropertyType() { return newRow.getType(); } @Override public Object getValue() { if (columnId==ColumnId.NAME) { return newRow.getName(); } else if (columnId==ColumnId.VALUE) { final int propertyType = newRow.getType(); if (propertyType==PropertyType.BOOLEAN) { try{ if (Boolean.parseBoolean(String.valueOf(newRow.getValue()))) { return 1; } else { return 0; } } catch(Exception e) { return 0; } } return newRow.getValue(); } else if (columnId==ColumnId.TYPE) { final int propertyType = newRow.getType(); if (propertyType!=-1) { return PropertyTypeSupport.indexOfPropertyType(propertyType); } else { //TODO: otherwise hardcode to STRING return PropertyTypeSupport.indexOfPropertyType(PropertyType.STRING); } } else if (columnId==ColumnId.MULTIPLE) { //TODO return 0; } else { return null; } } @Override public String getPropertyName() { return String.valueOf(newRow.getName()); } @Override public String getNewPropertyName() { return getPropertyName(); } @Override public void setValue(Object element, Object value) { if (getValue().equals(value)) { // then ignore this return; } if (columnId==ColumnId.NAME) { String newName = String.valueOf(value); ModifiableProperties props = getNode().getProperties(); int cnt = 1; while(props.getValue(newName)!=null) { newName = String.valueOf(value)+(cnt++); } newRow.setName(newName); } else if (columnId==ColumnId.VALUE) { newRow.setValue(PropertyTypeSupport.encodeValueAsString(value, getPropertyType())); } else if (columnId==ColumnId.TYPE) { int propertyType = PropertyTypeSupport.propertyTypeOfIndex((Integer)value); newRow.setType(propertyType); } else if (columnId==ColumnId.MULTIPLE) { // do nothing at the moment //TODO } else { // otherwise non-editable return; } handleNewRowUpdate(newRow); } } private class DecimalValidator implements ICellEditorValidator { private final CellEditor editor; DecimalValidator(CellEditor editor) { this.editor = editor; } @Override public String isValid(Object value) { Control cn = editor.getControl(); TableViewer tw = tableViewer; Color red = new Color(Display.getCurrent(), new RGB(255, 100, 100)); cn.setBackground(red); return null; } } public JcrEditingSupport(JcrPropertiesView view, TableViewer viewer, ColumnId columnType) { super(viewer); this.view = view; this.columnId = columnType; this.tableViewer = viewer; } @Override protected CellEditor getCellEditor(Object element) { try{ final CellEditor result = doGetCellEditor(element); if (result!=null) { Field field = asField(element); view.setLastValueEdited(field.getPropertyName(), field.getNewPropertyName(), columnId); } return result; } catch(Exception e) { e.printStackTrace(); return null; } } protected CellEditor doGetCellEditor(Object element) { if (!canEdit(element)) { return null; } switch(columnId) { case NAME: { // no validator needed - any string is OK return new TextCellEditor(tableViewer.getTable()); } case TYPE: { // using a dropdown editor final ComboBoxCellEditor editor = new ComboBoxCellEditor(tableViewer.getTable(), PropertyTypeSupport.PROPERTY_TYPES, SWT.NONE); editor.setActivationStyle(ComboBoxCellEditor.DROP_DOWN_ON_KEY_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_MOUSE_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_TRAVERSE_ACTIVATION); return editor; } case VALUE: { final Field field = asField(element); if (getNode().getProperty(field.getPropertyName()).isMultiple()) { // then launch the MVPEditor instead of returning an editor here return new MVNCellEditor(tableViewer.getTable(), getNode(), field.getPropertyName()); } if (field.getPropertyType()==PropertyType.DATE) { return new DateTimeCellEditor(tableViewer.getTable(), getNode(), field.getPropertyName()); } if (field.getPropertyType()==PropertyType.BOOLEAN) { return new ComboBoxCellEditor(tableViewer.getTable(), new String[] {"false", "true"}, SWT.READ_ONLY); } CellEditor editor; if (field.getPropertyName().equals("jcr:primaryType")) { editor = new TextCellEditor(tableViewer.getTable()) { @Override protected Control createControl(Composite parent) { Text text = (Text) super.createControl(parent); Repository repository = ServerUtil.getDefaultRepository(getNode().getProject()); NodeTypeRegistry ntManager = (repository==null) ? null : repository.getNodeTypeRegistry(); if (ntManager == null) { return text; } try { Collection<String> types = ntManager.getAllowedPrimaryChildNodeTypes(getNode().getParent().getPrimaryType()); SimpleContentProposalProvider proposalProvider = new SimpleContentProposalProvider(types.toArray(new String[0])); proposalProvider.setFiltering(true); ContentProposalAdapter adapter = new ContentProposalAdapter(text, new TextContentAdapter(), proposalProvider, null, null); adapter.setPropagateKeys(true); adapter .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); return text; } catch (RepositoryException e) { return text; } } }; } else { editor = new TextCellEditor(tableViewer.getTable()); } // value might require a validator depending on the property type int propertyType = getNode().getPropertyType(field.getPropertyName()); switch(propertyType) { case PropertyType.STRING: case PropertyType.NAME: { // no validator needed, any string is OK (for now) //TODO: check jcr rules for name break; } case PropertyType.DECIMAL: { editor.setValidator(new DecimalValidator(editor)); break; } default: { // for the rest, no check implemented yet //TODO break; } } return editor; } case MULTIPLE: { if (element instanceof NewRow) { return null; } return new ComboBoxCellEditor(tableViewer.getTable(), new String[] {"false", "true"}, SWT.READ_ONLY); } default: { throw new IllegalStateException("Unknown columnId: "+columnId); } } } @Override protected boolean canEdit(Object element) { return asField(element).canEdit(); } private Field asField(Object element) { if (element instanceof NewRow) { return new NewRowField((NewRow)element); } else { return new Field(element); } } @Override protected Object getValue(Object element) { return asField(element).getValue(); } @Override protected void setValue(Object element, Object value) { Field field = asField(element); if (!field.canEdit()) { return; } String newPropertyName; if (columnId==ColumnId.NAME) { newPropertyName = String.valueOf(value); } else { newPropertyName = field.getPropertyName(); } view.setLastValueEdited(field.getPropertyName(), newPropertyName, columnId); field.setValue(element, value); } void handleNewRowUpdate(NewRow newRow) { if (newRow.isComplete()) { tableViewer.remove(newRow); final JcrNode jcrNode = (JcrNode)tableViewer.getInput(); final Integer type = newRow.getType(); String encodeValueAsString = PropertyTypeSupport.encodeValueAsString(newRow.getValue(), type); if (type!=PropertyType.STRING && type!=PropertyType.NAME) { encodeValueAsString = "{"+PropertyType.nameFromValue(type)+"}"+encodeValueAsString; } final String propertyName = String.valueOf(newRow.getName()); jcrNode.addProperty(propertyName, encodeValueAsString); view.refreshContent(); } else { tableViewer.update(newRow, null); } } private JcrNode getNode() { return (JcrNode)tableViewer.getInput(); } }