Revision: 1312
Author: mathiasbr
Date: 2006-08-18 15:06:48 -0700 (Fri, 18 Aug 2006)
ViewCVS: http://svn.sourceforge.net/spring-rich-c/?rev=1312&view=rev
Log Message:
-----------
better utilization of converter to support conversion of custom collection
types.
ListModelConverter converts values to ListModel instances
documentation added
Modified Paths:
--------------
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/application/DefaultConversionService.java
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinder.java
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinding.java
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/ListBinding.java
Added Paths:
-----------
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/convert/support/ListModelConverter.java
Modified:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/application/DefaultConversionService.java
===================================================================
---
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/application/DefaultConversionService.java
2006-08-18 22:02:47 UTC (rev 1311)
+++
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/application/DefaultConversionService.java
2006-08-18 22:06:48 UTC (rev 1312)
@@ -24,6 +24,7 @@
import org.springframework.binding.format.FormatterLocator;
import org.springframework.binding.format.support.SimpleFormatterLocator;
import org.springframework.richclient.convert.support.CollectionConverter;
+import org.springframework.richclient.convert.support.ListModelConverter;
import org.springframework.util.StringUtils;
public class DefaultConversionService extends
org.springframework.binding.convert.support.DefaultConversionService {
@@ -41,6 +42,7 @@
addConverter(new BooleanToText());
addConverter(new TextToBoolean());
addConverter(new CollectionConverter());
+ addConverter(new ListModelConverter());
}
private FormatterLocator getFormatterLocator() {
@@ -57,16 +59,16 @@
}
public Class[] getSourceClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
public Class[] getTargetClasses() {
- return new Class[] {Date.class};
+ return new Class[] { Date.class };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
- return (!allowEmpty || StringUtils.hasText((String)source)) ?
getFormatterLocator().getDateTimeFormatter()
- .parseValue((String)source, Date.class) : null;
+ return (!allowEmpty || StringUtils.hasText((String) source)) ?
getFormatterLocator().getDateTimeFormatter()
+ .parseValue((String) source, Date.class) : null;
}
}
@@ -80,16 +82,16 @@
}
public Class[] getSourceClasses() {
- return new Class[] {Date.class};
+ return new Class[] { Date.class };
}
public Class[] getTargetClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
- return (!allowEmpty || source != null) ?
getFormatterLocator().getDateTimeFormatter().formatValue(
- source) : "";
+ return (!allowEmpty || source != null) ?
getFormatterLocator().getDateTimeFormatter().formatValue(source)
+ : "";
}
}
@@ -103,17 +105,17 @@
}
public Class[] getSourceClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
public Class[] getTargetClasses() {
- return new Class[] {Byte.class, Short.class, Integer.class,
Long.class, Float.class, Double.class,
- BigInteger.class, BigDecimal.class,};
+ return new Class[] { Byte.class, Short.class, Integer.class,
Long.class, Float.class, Double.class,
+ BigInteger.class, BigDecimal.class, };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
- return (!allowEmpty || StringUtils.hasText((String)source)) ?
getFormatterLocator().getNumberFormatter(
- targetClass).parseValue((String)source, targetClass) :
null;
+ return (!allowEmpty || StringUtils.hasText((String) source)) ?
getFormatterLocator().getNumberFormatter(
+ targetClass).parseValue((String) source, targetClass) :
null;
}
}
@@ -127,12 +129,12 @@
}
public Class[] getSourceClasses() {
- return new Class[] {Byte.class, Short.class, Integer.class,
Long.class, Float.class, Double.class,
- BigInteger.class, BigDecimal.class,};
+ return new Class[] { Byte.class, Short.class, Integer.class,
Long.class, Float.class, Double.class,
+ BigInteger.class, BigDecimal.class, };
}
public Class[] getTargetClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
@@ -172,35 +174,30 @@
}
public Class[] getSourceClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
public Class[] getTargetClasses() {
- return new Class[] {Boolean.class};
+ return new Class[] { Boolean.class };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
- String text = (String)source;
+ String text = (String) source;
if (!StringUtils.hasText(text)) {
return null;
- }
- else if (this.trueString != null &&
text.equalsIgnoreCase(this.trueString)) {
+ } else if (this.trueString != null &&
text.equalsIgnoreCase(this.trueString)) {
return Boolean.TRUE;
- }
- else if (this.falseString != null &&
text.equalsIgnoreCase(this.falseString)) {
+ } else if (this.falseString != null &&
text.equalsIgnoreCase(this.falseString)) {
return Boolean.FALSE;
- }
- else if (this.trueString == null
+ } else if (this.trueString == null
&& (text.equalsIgnoreCase(VALUE_TRUE) ||
text.equalsIgnoreCase(VALUE_ON)
|| text.equalsIgnoreCase(VALUE_YES) ||
text.equals(VALUE_1))) {
return Boolean.TRUE;
- }
- else if (this.falseString == null
+ } else if (this.falseString == null
&& (text.equalsIgnoreCase(VALUE_FALSE) ||
text.equalsIgnoreCase(VALUE_OFF)
|| text.equalsIgnoreCase(VALUE_NO) ||
text.equals(VALUE_0))) {
return Boolean.FALSE;
- }
- else {
+ } else {
throw new IllegalArgumentException("Invalid boolean value [" +
text + "]");
}
}
@@ -225,25 +222,22 @@
}
public Class[] getSourceClasses() {
- return new Class[] {Boolean.class};
+ return new Class[] { Boolean.class };
}
public Class[] getTargetClasses() {
- return new Class[] {String.class};
+ return new Class[] { String.class };
}
protected Object doConvert(Object source, Class targetClass) throws
Exception {
- Boolean bool = (Boolean)source;
+ Boolean bool = (Boolean) source;
if (this.trueString != null && bool.booleanValue()) {
return trueString;
- }
- else if (this.falseString != null && !bool.booleanValue()) {
+ } else if (this.falseString != null && !bool.booleanValue()) {
return falseString;
- }
- else if (bool.booleanValue()) {
+ } else if (bool.booleanValue()) {
return VALUE_YES;
- }
- else {
+ } else {
return VALUE_NO;
}
}
Added:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/convert/support/ListModelConverter.java
===================================================================
---
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/convert/support/ListModelConverter.java
(rev 0)
+++
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/convert/support/ListModelConverter.java
2006-08-18 22:06:48 UTC (rev 1312)
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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 org.springframework.richclient.convert.support;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.ListModel;
+
+import org.springframework.binding.convert.support.AbstractConverter;
+import org.springframework.binding.value.support.ListListModel;
+import org.springframework.core.ReflectiveVisitorHelper;
+
+/**
+ * @author Mathias Broekelmann
+ *
+ */
+public class ListModelConverter extends AbstractConverter {
+
+ private static final Class[] TARGET_CLASSES = new Class[] {
ListModel.class };
+
+ private static final Class[] SOURCE_CLASSES = new Class[] {
Collection.class, List.class, Object[].class,
+ ListModel.class, Object.class };
+
+ private final ReflectiveVisitorHelper visitorHelper = new
ReflectiveVisitorHelper();
+
+ protected Object doConvert(Object sourceValue, Class targetClass) throws
Exception {
+ return visitorHelper.invokeVisit(this, sourceValue);
+ }
+
+ public Class[] getSourceClasses() {
+ return SOURCE_CLASSES;
+ }
+
+ public Class[] getTargetClasses() {
+ return TARGET_CLASSES;
+ }
+
+ ListModel visit(ListModel listModel) {
+ return listModel;
+ }
+
+ ListModel visit(List list) {
+ return new ListListModel(list);
+ }
+
+ ListModel visit(Collection collection) {
+ return visit(new ArrayList(collection));
+ }
+
+ ListModel visit(Object[] array) {
+ return visit(Arrays.asList(array));
+ }
+
+ ListModel visit(Object object) {
+ return visit(new Object[] { object });
+ }
+}
Property changes on:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/convert/support/ListModelConverter.java
___________________________________________________________________
Name: svn:keywords
+ Revision Author Date Id
Name: svn:eol-style
+ native
Modified:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinder.java
===================================================================
---
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinder.java
2006-08-18 22:02:47 UTC (rev 1311)
+++
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinder.java
2006-08-18 22:06:48 UTC (rev 1312)
@@ -17,25 +17,62 @@
import java.util.Comparator;
import java.util.Map;
+import java.util.Observable;
import javax.swing.JComponent;
+import javax.swing.ListModel;
import org.springframework.binding.form.FormModel;
import org.springframework.core.closure.Closure;
import org.springframework.core.closure.Constraint;
import org.springframework.richclient.form.binding.Binding;
import org.springframework.richclient.form.binding.support.AbstractBinder;
+import org.springframework.util.Assert;
/**
+ * Abstract base class for list bindings.
+ * <p>
+ * The property <code>selectableItems</code> is used as a source of list
values. the value of
+ * <code>selectableItems</code> is converted to [EMAIL PROTECTED] ListModel}
by using the conversion service
+ * <p>
+ * The items of <code>selectableItems</code> can be filtered by defining a
constraint for <code>filter</code> and
+ * sorted by defining <code>comparator</code>. Dynamic filtering and sorting
is supported if the values of
+ * <code>filter</code> or <code>comparator</code> implements [EMAIL PROTECTED]
Observable}
+ * <p>
+ * Every value for <code>selectableItems</code>, <code>filter</code> and
<code>comparator</code> can be defined
+ * for all created binding instances or by using a context map where the keys
<code>SELECTABLE_ITEMS_KEY</code>,
+ * <code>COMPARATOR_KEY</code> and <code>FILTER_KEY</code> can be used for
specifying the context dependent values.
+ * <p>
+ * If the context values of <code>SELECTABLE_ITEMS_KEY</code>,
<code>COMPARATOR_KEY</code> and
+ * <code>FILTER_KEY</code> implement [EMAIL PROTECTED] Closure} it will be
used to create the instance for the value by passing
+ * the value of <code>selectableItems</code>, <code>filter</code> or
<code>comparator</code> as the argument.<br/>
+ * Please take into account that the argument for the closure can be null if
the property is not set. This feature is
+ * usefull to chain filter constraints.
+ * <p>
+ * Subclasses must implement [EMAIL PROTECTED] #createListBinding(JComponent,
FormModel, String)} which creates the instance for
+ * [EMAIL PROTECTED] AbstractListBinding}. [EMAIL PROTECTED]
#applyContext(AbstractListBinding, Map)} can be overwritten to apply additional
+ * context values
+ *
* @author Mathias Broekelmann
*
*/
public abstract class AbstractListBinder extends AbstractBinder {
+ /**
+ * key for defining a context dependent selectableItems value. See class
description for details.
+ */
public static final String SELECTABLE_ITEMS_KEY = "selectableItems";
+ /**
+ * key for defining a context dependent comparator value that is used to
sort the elements of selectableItems.
+ * Values must be instances of [EMAIL PROTECTED] Comparator}. See class
description for more details.
+ */
public static final String COMPARATOR_KEY = "comparator";
+ /**
+ * key for defining a context dependent filter constraint value that is
used to filter the elements of
+ * selectableItems. Values must be instances of [EMAIL PROTECTED]
Constraint}. See class description for more details.
+ */
public static final String FILTER_KEY = "filter";
private Object selectableItems;
@@ -44,76 +81,154 @@
private Constraint filter;
+ /**
+ * Creates a new instance by defining a type for the form fields and using
the default context keys
+ * <code>SELECTABLE_ITEMS_KEY</code>, <code>COMPARATOR_KEY</code> and
<code>FILTER_KEY</code>
+ *
+ * @param requiredSourceClass
+ * the type of the form fields to bind, if null form fields can
have any type
+ */
public AbstractListBinder(Class requiredSourceClass) {
this(requiredSourceClass, new String[] { SELECTABLE_ITEMS_KEY,
COMPARATOR_KEY, FILTER_KEY });
}
+ /**
+ * Creates a new instance by defining a type for the form fields and using
the given context keys
+ *
+ * @param requiredSourceClass
+ * the type of the form fields to bind, if null form fields can
have any type
+ * @param supportedContextKeys
+ * the keys which can be defined as context values
+ *
+ * @throws NullPointerException
+ * if supportedContextKeys is null
+ */
public AbstractListBinder(Class requiredSourceClass, String[]
supportedContextKeys) {
super(requiredSourceClass, supportedContextKeys);
}
+ /**
+ * Returns the [EMAIL PROTECTED] Comparator} which is used for bindings.
The value can be overwritten with a context value for
+ * <code>COMPARATOR_KEY</code>
+ *
+ * @return the comparator. If null no comparator is defined
+ */
public Comparator getComparator() {
return comparator;
}
+ /**
+ * Defines the [EMAIL PROTECTED] Comparator} which is used for bindings.
The value can be overwritten with a context value for
+ * <code>COMPARATOR_KEY</code>
+ *
+ * @param comparator
+ * the comparator. If null no comparator will be used
+ */
public void setComparator(Comparator comparator) {
this.comparator = comparator;
}
+ /**
+ * Returns the [EMAIL PROTECTED] Constraint} which is used as a filter for
the selectable items. The value can be overwritten
+ * with a context value for <code>FILTER_KEY</code>
+ *
+ * @return the filter. If null no filter is defined
+ */
public Constraint getFilter() {
return filter;
}
+ /**
+ * Defines the [EMAIL PROTECTED] Constraint} which is used as a filter for
the selectable items. The value can be overwritten
+ * with a context value for <code>FILTER_KEY</code>
+ *
+ * @param filter
+ * the filter constraint. If null no filter will be used
+ */
public void setFilter(Constraint filter) {
this.filter = filter;
}
+ /**
+ * Returns the selectable items which where used as a source for the
selectable items. The value can be overwritten
+ * with a context value for <code>SELECTABLE_ITEMS_KEY</code>
+ *
+ * @return the selectable items. If null no selectable items will be used
+ */
public Object getSelectableItems() {
return selectableItems;
}
+ /**
+ * Defines the selectable items which where used as a source for the
selectable items. The value can be overwritten
+ * with a context value for <code>SELECTABLE_ITEMS_KEY</code>
+ *
+ * @param selectableItems
+ * the selectable items. If null no selectable items will be
used
+ */
public void setSelectableItems(Object selectableItems) {
this.selectableItems = selectableItems;
}
+ /**
+ * Creates the binding and applies any context values
+ */
protected final Binding doBind(JComponent control, FormModel formModel,
String formPropertyPath, Map context) {
AbstractListBinding binding = createListBinding(control, formModel,
formPropertyPath);
+ Assert.notNull(binding);
applyContext(binding, context);
return binding;
}
+ /**
+ * Called to create the binding instance
+ *
+ * @param control
+ * the control to bind
+ * @param formModel
+ * the formmodel with the value of the
<code>formPropertyPath</code> field
+ * @param formPropertyPath
+ * the field path to bind
+ * @return the binding instance. Must not be null
+ */
protected abstract AbstractListBinding createListBinding(JComponent
control, FormModel formModel,
String formPropertyPath);
/**
+ * Applies any context or preset value.
+ *
* @param binding
+ * the binding to apply the values
* @param context
+ * contains context dependent values
*/
protected void applyContext(AbstractListBinding binding, Map context) {
if (context.containsKey(SELECTABLE_ITEMS_KEY)) {
- binding.setSelectableItems(context.get(SELECTABLE_ITEMS_KEY));
+
binding.setSelectableItems(decorate(context.get(SELECTABLE_ITEMS_KEY),
selectableItems));
} else if (selectableItems != null) {
binding.setSelectableItems(selectableItems);
}
if (context.containsKey(COMPARATOR_KEY)) {
- binding.setComparator((Comparator) context.get(COMPARATOR_KEY));
+ binding.setComparator((Comparator)
decorate(context.get(COMPARATOR_KEY), comparator));
} else if (comparator != null) {
binding.setComparator(comparator);
}
if (context.containsKey(FILTER_KEY)) {
- binding.setFilter((Constraint) context.get(FILTER_KEY));
+ binding.setFilter((Constraint) decorate(context.get(FILTER_KEY),
filter));
} else if (filter != null) {
binding.setFilter(filter);
}
}
/**
- * Decorates an object instance if the closure param is an instance of
[EMAIL PROTECTED] Closure}.
+ * Decorates an object instance if the <code>closure</code> value is an
instance of [EMAIL PROTECTED] Closure}.
*
* @param closure
- * the closure which is used to decorate the object
+ * the closure which is used to decorate the object.
* @param object
- * @return
+ * the value to decorate
+ * @return the decorated instance if <code>closure</code> implements
[EMAIL PROTECTED] Closure}, otherwise the value of
+ * <code>object</code>
*/
protected Object decorate(Object closure, Object object) {
if (closure instanceof Closure) {
Modified:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinding.java
===================================================================
---
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinding.java
2006-08-18 22:02:47 UTC (rev 1311)
+++
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/AbstractListBinding.java
2006-08-18 22:06:48 UTC (rev 1312)
@@ -17,20 +17,16 @@
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Comparator;
-import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JComponent;
import javax.swing.ListModel;
+import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.form.FormModel;
import org.springframework.binding.value.ValueModel;
-import org.springframework.binding.value.support.ListListModel;
import org.springframework.core.ReflectiveVisitorHelper;
import org.springframework.core.closure.Constraint;
import org.springframework.richclient.form.binding.support.AbstractBinding;
@@ -174,10 +170,6 @@
protected class SelectableItemsVisitor {
- ListModel visit(ListModel listModel) {
- return listModel;
- }
-
ListModel visit(ValueModel valueModel) {
Assert.notNull(valueModel.getValue(),
"value of ValueModel must not be null. Use an empty
Collection or Array");
@@ -185,20 +177,8 @@
return new ValueModelFilteredListModel(model, valueModel);
}
- ListModel visit(List list) {
- return new ListListModel(list);
- }
-
- ListModel visit(Collection collection) {
- return visit(new ArrayList(collection));
- }
-
- ListModel visit(Object[] array) {
- return visit(Arrays.asList(array));
- }
-
ListModel visit(Object object) {
- return visit(new Object[] { object });
+ return (ListModel) convertValue(object, ListModel.class);
}
ListModel visitNull() {
@@ -226,6 +206,24 @@
return comparator;
}
+ /**
+ * Converts the given object value into the given targetClass
+ *
+ * @param value
+ * the value to convert
+ * @param targetClass
+ * the target class to convert the value to
+ * @return the converted value
+ *
+ * @throws ConversionException
+ * if the value can not be converted
+ */
+ protected Object convertValue(Object value, Class targetClass) throws
ConversionException {
+ Assert.notNull(value);
+ Assert.notNull(targetClass);
+ return getConversionService().getConversionExecutor(value.getClass(),
targetClass).execute(value);
+ }
+
protected abstract ListModel getDefaultModel();
public Constraint getFilter() {
Modified:
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/ListBinding.java
===================================================================
---
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/ListBinding.java
2006-08-18 22:02:47 UTC (rev 1311)
+++
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/binding/swing/ListBinding.java
2006-08-18 22:06:48 UTC (rev 1312)
@@ -30,21 +30,18 @@
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.form.FormModel;
-import org.springframework.core.ReflectiveVisitorHelper;
import org.springframework.util.Assert;
public class ListBinding extends AbstractListBinding {
+ private static final Object[] EMPTY_VALUES = new Object[0];
+
private final ListSelectionListener selectionListener = new
SelectionListener();
private final PropertyChangeListener valueModelListener = new
ValueModelListener();
private ConversionExecutor conversionExecutor;
- private final ReflectiveVisitorHelper visitorHelper = new
ReflectiveVisitorHelper();
-
- private final Object selectedValuesVisitor = new SelectedValuesVisitor();
-
boolean selectingValues;
public ListBinding(JList list, FormModel formModel, String formFieldPath,
Class requiredSourceClass) {
@@ -150,7 +147,11 @@
* Updates the selection model with the selected values from the value
model.
*/
protected void updateSelectedItemsFromValueModel() {
- Object[] selectedValues = (Object[])
visitorHelper.invokeVisit(selectedValuesVisitor, getValue());
+ Object value = getValue();
+ Object[] selectedValues = EMPTY_VALUES;
+ if(value != null) {
+ selectedValues = (Object[]) convertValue(value, Object[].class);
+ }
// flag is used to avoid a round trip while we are selecting the values
selectingValues = true;
@@ -233,26 +234,6 @@
}
- protected static class SelectedValuesVisitor {
- private static final Object[] EMPTY_VALUES = new Object[0];
-
- Object[] visit(Object[] values) {
- return values;
- }
-
- Object[] visit(Collection values) {
- return values.toArray();
- }
-
- Object[] visit(Object value) {
- return new Object[] { value };
- }
-
- Object[] visitNull() {
- return EMPTY_VALUES;
- }
- }
-
protected ListModel getDefaultModel() {
return getList().getModel();
}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
spring-rich-c-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/spring-rich-c-cvs